| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { useParams } from 'common'
- import Link from 'next/link'
- import { useRouter } from 'next/router'
- import { parseAsString, useQueryState } from 'nuqs'
- import { useCallback } from 'react'
- import { Button } from 'ui'
- import { Admonition, GenericSkeletonLoader } from 'ui-patterns'
- import DeleteConfirmationDialogs from './DeleteConfirmationDialogs'
- import { SidePanelEditor } from './SidePanelEditor/SidePanelEditor'
- import { TableDefinition } from './TableDefinition'
- import { BrivenGrid } from '@/components/grid/BrivenGrid'
- import { useSyncTableEditorStateFromLocalStorageWithUrl } from '@/components/grid/BrivenGrid.utils'
- import {
- Entity,
- isForeignTable,
- isMaterializedView,
- isTableLike,
- isView,
- TableLike,
- } from '@/data/table-editor/table-editor-types'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
- import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState'
- import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas'
- import { TableEditorTableStateContextProvider } from '@/state/table-editor-table'
- import { createTabId, useTabsStateSnapshot } from '@/state/tabs'
- export interface TableGridEditorProps {
- isLoadingSelectedTable?: boolean
- selectedTable?: Entity
- }
- export const TableGridEditor = ({
- isLoadingSelectedTable = false,
- selectedTable,
- }: TableGridEditorProps) => {
- const router = useRouter()
- const { ref: projectRef, id } = useParams()
- const { setLastVisitedTable } = useDashboardHistory()
- const { selectedSchema } = useQuerySchemaState()
- const tabs = useTabsStateSnapshot()
- useSyncTableEditorStateFromLocalStorageWithUrl({
- projectRef,
- table: selectedTable,
- })
- const [selectedView] = useQueryState('view', parseAsString.withDefault('data'))
- const { can: canEditTables } = useAsyncCheckPermissions(
- PermissionAction.TENANT_SQL_ADMIN_WRITE,
- 'tables'
- )
- const { can: canEditColumns } = useAsyncCheckPermissions(
- PermissionAction.TENANT_SQL_ADMIN_WRITE,
- 'columns'
- )
- const isReadOnly = !canEditTables && !canEditColumns
- const tabId = !!id ? tabs.openTabs.find((x) => x.endsWith(id)) : undefined
- const openTabs = tabs.openTabs.filter((x) => !x.startsWith('sql'))
- const onTableCreated = useCallback(
- (table: { id: number }) => {
- router.push(
- `/project/${projectRef}/editor/${table.id}${!!selectedSchema ? `?schema=${selectedSchema}` : ''}`
- )
- },
- [projectRef, router, selectedSchema]
- )
- const onTableDeleted = useCallback(async () => {
- // For simplicity for now, we just open the first table within the same schema
- if (selectedTable) {
- // Close tab
- const tabId = createTabId(selectedTable.entity_type, { id: selectedTable.id })
- tabs.handleTabClose({
- id: tabId,
- router,
- editor: 'table',
- onClearDashboardHistory: () => setLastVisitedTable(undefined),
- })
- }
- }, [router, selectedTable, setLastVisitedTable, tabs])
- const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedTable?.schema ?? '' })
- // NOTE: DO NOT PUT HOOKS AFTER THIS LINE
- if (isLoadingSelectedTable || !projectRef) {
- return (
- <div className="flex flex-col">
- <div className="h-10 bg-dash-sidebar dark:bg-surface-100" />
- <div className="h-9 border-y" />
- <div className="p-2 col-span-full">
- <GenericSkeletonLoader />
- </div>
- </div>
- )
- }
- const isViewSelected = isView(selectedTable) || isMaterializedView(selectedTable)
- const isTableSelected = isTableLike(selectedTable)
- const isForeignTableSelected = isForeignTable(selectedTable)
- const canEditViaTableEditor = isTableSelected && !isSchemaLocked
- const editable = !isReadOnly && canEditViaTableEditor
- const gridKey = !!selectedTable
- ? `${selectedTable.schema}_${selectedTable.name}`
- : 'unknown-table'
- /** [Joshen] We're going to need to refactor BrivenGrid eventually to make the code here more readable
- * For context we previously built the BrivenGrid as a reusable npm component, but eventually decided
- * to just integrate it directly into the dashboard. The header, and body (+footer) should be decoupled.
- */
- return (
- // When any click happens in a table tab, the tab becomes permanent
- <div className="h-full" onClick={() => tabs.makeActiveTabPermanent()}>
- {!selectedTable ? (
- <div className="flex items-center justify-center h-full">
- <div className="w-[400px]">
- <Admonition
- type="default"
- title={`Unable to find your table with ID ${id}`}
- description="This table doesn't exist in your database"
- >
- {!!tabId ? (
- <Button
- type="default"
- className="mt-2"
- onClick={() => {
- tabs.handleTabClose({
- id: tabId,
- router,
- editor: 'table',
- onClearDashboardHistory: () => setLastVisitedTable(undefined),
- })
- }}
- >
- Close tab
- </Button>
- ) : openTabs.length > 0 ? (
- <Button
- asChild
- type="default"
- className="mt-2"
- onClick={() => setLastVisitedTable(undefined)}
- >
- <Link href={`/project/${projectRef}/editor/${openTabs[0].split('-')[1]}`}>
- Close tab
- </Link>
- </Button>
- ) : (
- <Button
- asChild
- type="default"
- className="mt-2"
- onClick={() => setLastVisitedTable(undefined)}
- >
- <Link href={`/project/${projectRef}/editor`}>Head back</Link>
- </Button>
- )}
- </Admonition>
- </div>
- </div>
- ) : (
- <TableEditorTableStateContextProvider
- key={`table-editor-table-${selectedTable.id}`}
- projectRef={projectRef}
- table={selectedTable}
- editable={editable}
- >
- <BrivenGrid
- key={gridKey}
- gridProps={{ height: '100%' }}
- customHeader={
- (isViewSelected || isTableSelected) && selectedView === 'definition' ? (
- <div className="px-2 flex items-center gap-x-2">
- <p>
- SQL Definition of <code className="text-sm">{selectedTable.name}</code>{' '}
- </p>
- <p className="text-foreground-light text-sm">(Read only)</p>
- </div>
- ) : null
- }
- >
- {(isViewSelected || isTableSelected) && selectedView === 'definition' && (
- <TableDefinition entity={selectedTable} />
- )}
- </BrivenGrid>
- <DeleteConfirmationDialogs
- selectedTable={isTableSelected ? selectedTable : undefined}
- onTableDeleted={onTableDeleted}
- />
- </TableEditorTableStateContextProvider>
- )}
- <SidePanelEditor
- editable={editable}
- selectedTable={
- isTableSelected || isForeignTableSelected ? (selectedTable as TableLike) : undefined
- }
- onTableCreated={onTableCreated}
- />
- </div>
- )
- }
|