mirror of
https://github.com/supabase/supabase.git
synced 2026-09-07 02:20:52 +08:00
Follow-up to #48344: collapses the two resolution paths for the Admonition module into one. `src/admonition.tsx` was a back-compat shim re-exporting `src/Admonition/`. Two ways to resolve one module is exactly what produced the macOS self-import bug fixed in #48344, and the local typecheck errors that #48374 worked around. This removes the shim and standardizes on the PascalCase subpath, matching every other export in the package. **Changed:** - Codemodded all 246 `ui-patterns/admonition` imports to `ui-patterns/Admonition` (240 `.tsx`, 5 `.mdx`, 1 `.ts` across studio, docs, www, design-system, and lite-studio) - Pointed the 5 internal `'../admonition'` imports back at the `'../Admonition'` directory **Removed:** - `packages/ui-patterns/src/admonition.tsx`, and its `./admonition` entry in the exports map (regenerated with `pnpm gen:exports`) ## To test - `grep -r "ui-patterns/admonition" --include='*.ts*'` → no hits - `pnpm test:case-hazards` → passes - `pnpm typecheck` → all 15 tasks green - `pnpm --filter studio run lint:ratchet` → passes - `pnpm --filter ui-patterns vitest run src/Admonition` → 11 tests pass <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Standardized Admonition component imports across the application and documentation. * Improved compatibility with case-sensitive environments by using the canonical component path. * Removed the legacy Admonition import entry point. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
213 lines
7.4 KiB
TypeScript
213 lines
7.4 KiB
TypeScript
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 } from 'ui-patterns/Admonition'
|
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import DeleteConfirmationDialogs from './DeleteConfirmationDialogs'
|
|
import { SidePanelEditor } from './SidePanelEditor/SidePanelEditor'
|
|
import { TableDefinition } from './TableDefinition'
|
|
import { SupabaseGrid } from '@/components/grid/SupabaseGrid'
|
|
import { useSyncTableEditorStateFromLocalStorageWithUrl } from '@/components/grid/SupabaseGrid.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 SupabaseGrid eventually to make the code here more readable
|
|
* For context we previously built the SupabaseGrid 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
|
|
variant="default"
|
|
className="mt-2"
|
|
onClick={() => {
|
|
tabs.handleTabClose({
|
|
id: tabId,
|
|
router,
|
|
editor: 'table',
|
|
onClearDashboardHistory: () => setLastVisitedTable(undefined),
|
|
})
|
|
}}
|
|
>
|
|
Close tab
|
|
</Button>
|
|
) : openTabs.length > 0 ? (
|
|
<Button
|
|
asChild
|
|
variant="default"
|
|
className="mt-2"
|
|
onClick={() => setLastVisitedTable(undefined)}
|
|
>
|
|
<Link href={`/project/${projectRef}/editor/${openTabs[0].split('-')[1]}`}>
|
|
Close tab
|
|
</Link>
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
asChild
|
|
variant="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}
|
|
>
|
|
<SupabaseGrid
|
|
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} />
|
|
)}
|
|
</SupabaseGrid>
|
|
|
|
<DeleteConfirmationDialogs
|
|
selectedTable={selectedTable}
|
|
onTableDeleted={onTableDeleted}
|
|
/>
|
|
</TableEditorTableStateContextProvider>
|
|
)}
|
|
|
|
<SidePanelEditor
|
|
editable={editable}
|
|
selectedTable={
|
|
isTableSelected || isForeignTableSelected ? (selectedTable as TableLike) : undefined
|
|
}
|
|
onTableCreated={onTableCreated}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|