Clean up tabs interface feature flags (#36441)

* Clean up tabs interface feature flags

* Update SQL e2e test
This commit is contained in:
Joshen Lim authored and GitHub committed 2025-06-23 11:16:24 +08:00
1 parent 4945ba1861
commit 327cab053a
29 files changed
+213 -653

No files matched your search

@@ -8,20 +8,6 @@ export const FEATURE_PREVIEWS = [
isNew: true,
isPlatformOnly: false,
},
{
key: LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS,
name: 'Table Editor Tabs',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/35636',
isNew: true,
isPlatformOnly: false,
},
{
key: LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS,
name: 'SQL Editor Tabs',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/35636',
isNew: true,
isPlatformOnly: true,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL,
name: 'Project API documentation',
@@ -2,8 +2,6 @@ import { noop } from 'lodash'
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'
import { FeatureFlagContext, LOCAL_STORAGE_KEYS } from 'common'
import { useFlag } from 'hooks/ui/useFlag'
import { IS_PLATFORM } from 'lib/constants'
import { EMPTY_OBJ } from 'lib/void'
import { FEATURE_PREVIEWS } from './FeaturePreview.constants'
@@ -21,15 +19,10 @@ export const useFeaturePreviewContext = () => useContext(FeaturePreviewContext)
export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{}>) => {
const { hasLoaded } = useContext(FeatureFlagContext)
const enableTabsInterface = useFlag('tabsInterface')
// [Joshen] Similar logic to feature flagging previews, we can use flags to default opt in previews
const isDefaultOptIn = (feature: (typeof FEATURE_PREVIEWS)[number]) => {
switch (feature.key) {
case LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS:
return enableTabsInterface
case LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS:
return enableTabsInterface
default:
return false
}
@@ -86,14 +79,3 @@ export const useIsInlineEditorEnabled = () => {
const { flags } = useFeaturePreviewContext()
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_INLINE_EDITOR]
}
export const useIsTableEditorTabsEnabled = () => {
const { flags } = useFeaturePreviewContext()
return flags[LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS]
}
export const useIsSQLEditorTabsEnabled = () => {
const { flags } = useFeaturePreviewContext()
if (!IS_PLATFORM) return false
return flags[LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS]
}
@@ -14,15 +14,11 @@ import { CLSPreview } from './CLSPreview'
import { FEATURE_PREVIEWS } from './FeaturePreview.constants'
import { useFeaturePreviewContext } from './FeaturePreviewContext'
import { InlineEditorPreview } from './InlineEditorPreview'
import { SqlEditorTabsPreview } from './SqlEditorTabs'
import { TableEditorTabsPreview } from './TableEditorTabs'
const FEATURE_PREVIEW_KEY_TO_CONTENT: {
[key: string]: ReactNode
} = {
[LOCAL_STORAGE_KEYS.UI_PREVIEW_INLINE_EDITOR]: <InlineEditorPreview />,
[LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS]: <TableEditorTabsPreview />,
[LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS]: <SqlEditorTabsPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]: <APISidePanelPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_CLS]: <CLSPreview />,
}
@@ -1,7 +1,6 @@
import Image from 'next/image'
import { BASE_PATH } from 'lib/constants'
import { Admonition } from 'ui-patterns'
export const InlineEditorPreview = () => {
return (
@@ -1,28 +0,0 @@
import { BASE_PATH } from 'lib/constants'
import Image from 'next/image'
export const SqlEditorTabsPreview = () => {
return (
<div className="space-y-2">
<Image
src={`${BASE_PATH}/img/previews/tabs-sql.png`}
width={1860}
height={970}
alt="api-docs-side-panel-preview"
className="rounded border"
/>
<p className="text-foreground-light text-sm">
The SQL Editor now features tabs for improved navigation and organization. Conveniently go
between the queries that you're focused on, and collapse the sidebar for a bigger real
estate while writing your queries.
</p>
<div className="space-y-2 !mt-4">
<p className="text-sm">Enabling this preview will:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>Enable opening of queries in the SQL Editor as tabs</li>
<li>Support closing of the navigation sidebar for a larger code editor space</li>
</ul>
</div>
</div>
)
}
@@ -1,192 +0,0 @@
import { ExternalLink, X } from 'lucide-react'
import Image from 'next/image'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useEditorType } from 'components/layouts/editors/EditorsLayout.hooks'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { InlineLinkClassName } from 'components/ui/InlineLink'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { BASE_PATH } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import {
Alert_Shadcn_,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Badge,
Button,
cn,
HoverCard,
HoverCardContent_Shadcn_,
HoverCardTrigger_Shadcn_,
} from 'ui'
import { useIsSQLEditorTabsEnabled, useIsTableEditorTabsEnabled } from './FeaturePreviewContext'
export const TableEditorTabsPreview = () => {
return (
<div className="space-y-2">
<Image
src={`${BASE_PATH}/img/previews/tabs-editor.png`}
width={1860}
height={970}
alt="api-docs-side-panel-preview"
className="rounded border"
/>
<p className="text-foreground-light text-sm">
The Table Editor now features tabs for improved navigation and organization. Have multiple
tables opened across schemas and conveniently go across them without having to switch
schemas. Collapse the sidebar for a bigger real estate while browsing your data.
</p>
<div className="space-y-2 !mt-4">
<p className="text-sm">Enabling this preview will:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>Enable opening of tables in the Table Editor as tabs</li>
<li>Support closing of the navigation sidebar for a larger data grid space</li>
</ul>
</div>
</div>
)
}
export const TabsUpdateCallout = () => {
const editor = useEditorType()
const appSnap = useAppStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const isTabsEnabled = isTableEditorTabsEnabled || isSQLEditorTabsEnabled
const [tableEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS,
''
)
const [sqlEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS,
''
)
const [tabsInterfaceAcknowledge, setTabsInterfaceAcknowledge] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.TABS_INTERFACE_ACKNOWLEDGED,
false
)
const isDefaultOptedInTabs =
isTabsEnabled && tableEditorTabsPreviewState === '' && sqlEditorTabsPreviewState === ''
if (!isDefaultOptedInTabs || tabsInterfaceAcknowledge) return null
return (
<Alert_Shadcn_ className="mb-4 relative">
<AlertTitle_Shadcn_>
<Badge variant="brand" className="mr-2">
NEW
</Badge>
Tabs Interface for Editors
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
The Table and SQL Editors now feature tabs for improved navigation and organization! Check
out our{' '}
<span
className={cn(InlineLinkClassName, 'cursor-pointer')}
onClick={() => {
appSnap.setSelectedFeaturePreview(
editor === 'table'
? LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS
: LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS
)
appSnap.setShowFeaturePreviewModal(true)
}}
>
feature previews
</span>{' '}
for more information.
</AlertDescription_Shadcn_>
<AlertDescription_Shadcn_ className="mt-4 flex items-center gap-x-2">
<Button asChild type="default" icon={<ExternalLink />}>
<a
target="_blank"
rel="noreferrer noopener"
href="https://github.com/orgs/supabase/discussions/35636"
>
View announcement
</a>
</Button>
</AlertDescription_Shadcn_>
<ButtonTooltip
type="text"
icon={<X />}
className="absolute top-2 right-2 px-1"
onClick={() => setTabsInterfaceAcknowledge(true)}
tooltip={{ content: { side: 'bottom', text: 'Dismiss' } }}
/>
</Alert_Shadcn_>
)
}
export const TabsUpdateTooltip = () => {
const editor = useEditorType()
const appSnap = useAppStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const isTabsEnabled = isTableEditorTabsEnabled || isSQLEditorTabsEnabled
const [tableEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS,
''
)
const [sqlEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS,
''
)
const [tabsInterfaceAcknowledge, setTabsInterfaceAcknowledge] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.TABS_INTERFACE_ACKNOWLEDGED,
false
)
const isDefaultOptedInTabs =
isTabsEnabled && tableEditorTabsPreviewState === '' && sqlEditorTabsPreviewState === ''
if (!isDefaultOptedInTabs || tabsInterfaceAcknowledge) return null
return (
<HoverCard openDelay={150}>
<HoverCardTrigger_Shadcn_ asChild>
<div
className={cn(
'flex items-center justify-center px-2 h-10 shrink-0',
'border-b border-b-default gap-x-2'
)}
>
<Badge variant="brand" className="rounded">
<span>NEW</span>
</Badge>
</div>
</HoverCardTrigger_Shadcn_>
<HoverCardContent_Shadcn_ className="p-3 w-72">
<div className="flex flex-col gap-y-1">
<p className="text-sm">Tabs Interface for Editors</p>
<p className="text-xs text-foreground-light">
The Table and SQL Editors now feature tabs for improved navigation and organization!
Check out our{' '}
<span
className={cn(InlineLinkClassName, 'cursor-pointer')}
onClick={() => {
appSnap.setSelectedFeaturePreview(
editor === 'table'
? LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS
: LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS
)
appSnap.setShowFeaturePreviewModal(true)
}}
>
feature previews
</span>{' '}
for more information.
</p>
</div>
<Button type="default" className="mt-2" onClick={() => setTabsInterfaceAcknowledge(true)}>
Dismiss
</Button>
</HoverCardContent_Shadcn_>
</HoverCard>
)
}
@@ -3,6 +3,7 @@ import { toast } from 'sonner'
import { useParams } from 'common'
import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { getContentById } from 'data/content/content-id-query'
import {
UpsertContentPayload,
@@ -11,12 +12,10 @@ import {
import { Snippet } from 'data/content/sql-folders-query'
import type { SqlSnippet } from 'data/content/sql-snippets-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import { AiIconAnimation, Button, Form, Input, Modal } from 'ui'
import { useIsSQLEditorTabsEnabled } from '../App/FeaturePreview/FeaturePreviewContext'
import { subscriptionHasHipaaAddon } from '../Billing/Subscription/Subscription.utils'
export interface RenameQueryModalProps {
@@ -42,7 +41,6 @@ const RenameQueryModal = ({
{ enabled: visible }
)
const isSQLSnippet = snippet.type === 'sql'
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const { data: projectSettings } = useProjectSettingsV2Query({ projectRef: ref })
// Customers on HIPAA plans should not have access to Supabase AI
@@ -112,10 +110,8 @@ const RenameQueryModal = ({
snapV2.renameSnippet({ id, name: nameInput, description: descriptionInput })
if (isSQLEditorTabsEnabled && ref) {
const tabId = createTabId('sql', { id })
tabsSnap.updateTab(tabId, { label: nameInput })
}
const tabId = createTabId('sql', { id })
tabsSnap.updateTab(tabId, { label: nameInput })
toast.success('Successfully renamed snippet!')
if (onComplete) onComplete()
@@ -51,7 +51,6 @@ import {
TooltipTrigger,
cn,
} from 'ui'
import { useIsSQLEditorTabsEnabled } from '../App/FeaturePreview/FeaturePreviewContext'
import { useSqlEditorDiff, useSqlEditorPrompt } from './hooks'
import { RunQueryWarningModal } from './RunQueryWarningModal'
import {
@@ -95,7 +94,6 @@ export const SQLEditor = () => {
const { isOptedInToAI, isHipaaProjectDisallowed } = useOrgOptedIntoAiAndHippaProject()
const [selectedSchemas] = useSchemasForAi(project?.ref!)
const includeSchemaMetadata = (isOptedInToAI && !isHipaaProjectDisallowed) || !IS_PLATFORM
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const {
sourceSqlDiff,
@@ -212,10 +210,8 @@ export const SQLEditor = () => {
try {
const { title: name } = await generateSqlTitle({ sql })
snapV2.renameSnippet({ id, name })
if (isSQLEditorTabsEnabled && ref) {
const tabId = createTabId('sql', { id })
tabs.updateTab(tabId, { label: name })
}
const tabId = createTabId('sql', { id })
tabs.updateTab(tabId, { label: name })
} catch (error) {
// [Joshen] No error handler required as this happens in the background and not necessary to ping the user
}
@@ -42,10 +42,7 @@ import {
Sidebar as SidebarPrimitive,
useSidebar,
} from 'ui'
import {
useIsAPIDocsSidePanelEnabled,
useIsSQLEditorTabsEnabled,
} from './App/FeaturePreview/FeaturePreviewContext'
import { useIsAPIDocsSidePanelEnabled } from './App/FeaturePreview/FeaturePreviewContext'
export const ICON_SIZE = 32
export const ICON_STROKE_WIDTH = 1.5
@@ -228,7 +225,6 @@ const ProjectLinks = () => {
const showWarehouse = useFlag('warehouse')
const showUnifiedLogs = useFlag('unifiedLogs')
const isSqlEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const activeRoute = router.pathname.split('/')[3]
@@ -244,9 +240,7 @@ const ProjectLinks = () => {
'realtime:all',
])
const toolRoutes = generateToolRoutes(ref, project, {
sqlEditorTabs: isSqlEditorTabsEnabled,
})
const toolRoutes = generateToolRoutes(ref, project)
const productRoutes = generateProductRoutes(ref, project, {
auth: authEnabled,
edgeFunctions: edgeFunctionsEnabled,
@@ -1,66 +0,0 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
import { PROTECTED_SCHEMAS } from 'lib/constants/schemas'
import { useTableEditorStateSnapshot } from 'state/table-editor'
export interface EmptyStateProps {}
const EmptyState = ({}: EmptyStateProps) => {
const snap = useTableEditorStateSnapshot()
const { selectedSchema } = useQuerySchemaState()
const isProtectedSchema = PROTECTED_SCHEMAS.includes(selectedSchema)
const canCreateTables =
useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') && !isProtectedSchema
const [sort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>(
'table-editor-sort',
'alphabetical'
)
const { project } = useProjectContext()
const { data } = useEntityTypesQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
schemas: [selectedSchema],
sort,
})
const totalCount = data?.pages?.[0].data.count ?? 0
return (
<div className="w-full h-full flex items-center justify-center">
{totalCount === 0 ? (
<ProductEmptyState
title="Table Editor"
ctaButtonLabel={canCreateTables ? 'Create a new table' : undefined}
onClickCta={canCreateTables ? snap.onAddTable : undefined}
>
<p className="text-sm text-foreground-light">
There are no tables available in this schema.
</p>
</ProductEmptyState>
) : (
<div className="flex flex-col items-center space-y-4">
<ProductEmptyState
title="Table Editor"
ctaButtonLabel={canCreateTables ? 'Create a new table' : undefined}
onClickCta={canCreateTables ? snap.onAddTable : undefined}
>
<p className="text-sm text-foreground-light">
Select a table from the navigation panel on the left to view its data
{canCreateTables && ', or create a new one.'}
</p>
</ProductEmptyState>
</div>
)}
</div>
)
}
export default EmptyState
@@ -5,7 +5,6 @@ import { useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { useIsTableEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useDatabasePublicationCreateMutation } from 'data/database-publications/database-publications-create-mutation'
import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query'
@@ -21,6 +20,7 @@ import { tableRowKeys } from 'data/table-rows/keys'
import { useTableRowCreateMutation } from 'data/table-rows/table-row-create-mutation'
import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation'
import { tableKeys } from 'data/tables/keys'
import { RetrieveTableResult } from 'data/tables/table-retrieve-query'
import { getTables } from 'data/tables/tables-query'
import { useUrlState } from 'hooks/ui/useUrlState'
import { useGetImpersonatedRoleState } from 'state/role-impersonation-state'
@@ -50,7 +50,6 @@ import {
import SpreadsheetImport from './SpreadsheetImport/SpreadsheetImport'
import TableEditor from './TableEditor/TableEditor'
import type { ImportContent } from './TableEditor/TableEditor.types'
import { RetrieveTableResult } from 'data/tables/table-retrieve-query'
export interface SidePanelEditorProps {
editable?: boolean
@@ -71,7 +70,6 @@ const SidePanelEditor = ({
const { ref } = useParams()
const snap = useTableEditorStateSnapshot()
const tabsSnap = useTabsStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] })
const queryClient = useQueryClient()
@@ -493,7 +491,7 @@ const SidePanelEditor = ({
`Table ${table.name} has been updated but there were some errors. Please check these errors separately.`
)
} else {
if (isTableEditorTabsEnabled && ref && payload.name) {
if (ref && payload.name) {
// [Joshen] Only table entities can be updated via the dashboard
const tabId = createTabId(ENTITY_TYPE.TABLE, { id: selectedTable.id })
tabsSnap.updateTab(tabId, { label: payload.name })
@@ -1,5 +1,5 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { isUndefined } from 'lodash'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useCallback } from 'react'
@@ -18,13 +18,11 @@ import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { useUrlState } from 'hooks/ui/useUrlState'
import { PROTECTED_SCHEMAS } from 'lib/constants/schemas'
import Link from 'next/link'
import { useAppStateSnapshot } from 'state/app-state'
import { TableEditorTableStateContextProvider } from 'state/table-editor-table'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import { Button } from 'ui'
import { Admonition, GenericSkeletonLoader } from 'ui-patterns'
import { useIsTableEditorTabsEnabled } from '../App/FeaturePreview/FeaturePreviewContext'
import DeleteConfirmationDialogs from './DeleteConfirmationDialogs'
import SidePanelEditor from './SidePanelEditor/SidePanelEditor'
import TableDefinition from './TableDefinition'
@@ -44,7 +42,6 @@ export const TableGridEditor = ({
const { ref: projectRef, id } = useParams()
const tabs = useTabsStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const { selectedSchema } = useQuerySchemaState()
useLoadTableEditorStateFromLocalStorageIntoUrl({
@@ -65,9 +62,9 @@ export const TableGridEditor = ({
connectionString: project?.connectionString,
})
const onClearDashboardHistory = () => {
const onClearDashboardHistory = useCallback(() => {
if (projectRef) appSnap.setDashboardHistory(projectRef, 'editor', undefined)
}
}, [appSnap, projectRef])
const onTableCreated = useCallback(
(table: { id: number }) => {
@@ -78,19 +75,12 @@ export const TableGridEditor = ({
const onTableDeleted = useCallback(async () => {
// For simplicity for now, we just open the first table within the same schema
if (isTableEditorTabsEnabled && selectedTable) {
if (selectedTable) {
// Close tab
const tabId = createTabId(selectedTable.entity_type, { id: selectedTable.id })
tabs.handleTabClose({ id: tabId, router, editor: 'table', onClearDashboardHistory })
} else {
const tables = await getTables(selectedSchema)
if (tables.length > 0) {
router.push(`/project/${projectRef}/editor/${tables[0].id}`)
} else {
router.push(`/project/${projectRef}/editor`)
}
}
}, [getTables, isTableEditorTabsEnabled, projectRef, router, selectedSchema])
}, [onClearDashboardHistory, router, selectedTable, tabs])
// NOTE: DO NOT PUT HOOKS AFTER THIS LINE
if (isLoadingSelectedTable || !projectRef) {
@@ -105,7 +95,7 @@ export const TableGridEditor = ({
)
}
if (isUndefined(selectedTable)) {
if (!selectedTable) {
return (
<div className="flex items-center justify-center h-full">
<div className="w-[400px]">
@@ -114,45 +104,41 @@ export const TableGridEditor = ({
title={`Unable to find your table with ID ${id}`}
description="This table doesn't exist in your database"
>
{isTableEditorTabsEnabled && (
<>
{!!tabId ? (
<Button
type="default"
className="mt-2"
onClick={() => {
tabs.handleTabClose({
id: tabId,
router,
editor: 'table',
onClearDashboardHistory,
})
}}
>
Close tab
</Button>
) : openTabs.length > 0 ? (
<Button
asChild
type="default"
className="mt-2"
onClick={() => appSnap.setDashboardHistory(projectRef, 'editor', undefined)}
>
<Link href={`/project/${projectRef}/editor/${openTabs[0].split('-')[1]}`}>
Close tab
</Link>
</Button>
) : (
<Button
asChild
type="default"
className="mt-2"
onClick={() => appSnap.setDashboardHistory(projectRef, 'editor', undefined)}
>
<Link href={`/project/${projectRef}/editor`}>Head back</Link>
</Button>
)}
</>
{!!tabId ? (
<Button
type="default"
className="mt-2"
onClick={() => {
tabs.handleTabClose({
id: tabId,
router,
editor: 'table',
onClearDashboardHistory,
})
}}
>
Close tab
</Button>
) : openTabs.length > 0 ? (
<Button
asChild
type="default"
className="mt-2"
onClick={() => appSnap.setDashboardHistory(projectRef, 'editor', undefined)}
>
<Link href={`/project/${projectRef}/editor/${openTabs[0].split('-')[1]}`}>
Close tab
</Link>
</Button>
) : (
<Button
asChild
type="default"
className="mt-2"
onClick={() => appSnap.setDashboardHistory(projectRef, 'editor', undefined)}
>
<Link href={`/project/${projectRef}/editor`}>Head back</Link>
</Button>
)}
</Admonition>
</div>
@@ -1,2 +1 @@
export { default as EmptyState } from './EmptyState'
export { default as SidePanelEditor } from './SidePanelEditor/SidePanelEditor'
@@ -19,12 +19,7 @@ import {
} from 'icons'
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
export const generateToolRoutes = (
ref?: string,
project?: Project,
features?: { sqlEditorTabs: boolean }
): Route[] => {
const { sqlEditorTabs } = features ?? {}
export const generateToolRoutes = (ref?: string, project?: Project, features?: {}): Route[] => {
const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP
const buildingUrl = `/project/${ref}`
@@ -42,8 +37,7 @@ export const generateToolRoutes = (
icon: <SqlEditor size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link: !IS_PLATFORM
? `/project/${ref}/sql/1`
: ref &&
(isProjectBuilding ? buildingUrl : `/project/${ref}/sql${!!sqlEditorTabs ? '' : '/new'}`),
: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/sql`),
},
]
}
@@ -4,10 +4,6 @@ import { useRouter } from 'next/router'
import { forwardRef, Fragment, PropsWithChildren, ReactNode, useEffect, useState } from 'react'
import { useParams } from 'common'
import {
useIsSQLEditorTabsEnabled,
useIsTableEditorTabsEnabled,
} from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import ProjectAPIDocs from 'components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
import { AIAssistant } from 'components/ui/AIAssistantPanel/AIAssistant'
import AISettingsModal from 'components/ui/AISettingsModal'
@@ -101,14 +97,8 @@ const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<ProjectLayout
} = useAppStateSnapshot()
const aiSnap = useAiAssistantStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
// For tabs preview flag logic - only conditionally collapse sidebar for table editor and sql editor if feature flags are on
const editor = useEditorType()
const tableEditorTabsEnabled = editor === 'table' && isTableEditorTabsEnabled
const sqlEditorTabsEnabled = editor === 'sql' && isSQLEditorTabsEnabled
const forceShowProductMenu = !tableEditorTabsEnabled && !sqlEditorTabsEnabled
const forceShowProductMenu = editor === undefined
const sideBarIsOpen = forceShowProductMenu || showSidebar
const projectName = selectedProject?.name
@@ -1,5 +1,9 @@
import { Eye, EyeOffIcon, Heart, Unlock } from 'lucide-react'
import { useRouter } from 'next/router'
import { useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { useIsSQLEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import DownloadSnippetModal from 'components/interfaces/SQLEditor/DownloadSnippetModal'
import { MoveQueryModal } from 'components/interfaces/SQLEditor/MoveQueryModal'
import RenameQueryModal from 'components/interfaces/SQLEditor/RenameQueryModal'
@@ -19,10 +23,6 @@ import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { useProfile } from 'lib/profile'
import uuidv4 from 'lib/uuid'
import { Eye, EyeOffIcon, Heart, Unlock } from 'lucide-react'
import { useRouter } from 'next/router'
import { useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import {
SnippetWithContent,
useSnippetFolders,
@@ -59,7 +59,6 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
const snapV2 = useSqlEditorV2StateSnapshot()
const tabs = useTabsStateSnapshot()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const [sectionVisibility, setSectionVisibility] = useLocalStorage<SectionState>(
LOCAL_STORAGE_KEYS.SQL_EDITOR_SECTION_STATE(projectRef ?? ''),
@@ -293,12 +292,10 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({
onSuccess: (data) => {
if (isSQLEditorTabsEnabled) {
// Update Tabs state - currently unknown how to differentiate between sql and non-sql content
// so we're just deleting all tabs for with matching IDs
const tabIds = data.map((id) => createTabId('sql', { id }))
tabs.removeTabs(tabIds)
}
// Update Tabs state - currently unknown how to differentiate between sql and non-sql content
// so we're just deleting all tabs for with matching IDs
const tabIds = data.map((id) => createTabId('sql', { id }))
tabs.removeTabs(tabIds)
},
onError: (error, data) => {
if (error.message.includes('Contents not found')) {
@@ -541,10 +538,10 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
const sqlEditorTabsCleanup = useSqlEditorTabsCleanup()
useEffect(() => {
if (isSuccess && isSQLEditorTabsEnabled) {
if (isSuccess) {
sqlEditorTabsCleanup({ snippets: allSnippetsInView as any })
}
}, [allSnippetsInView, isSQLEditorTabsEnabled, isSuccess, sqlEditorTabsCleanup])
}, [allSnippetsInView, isSuccess, sqlEditorTabsCleanup])
return (
<>
@@ -572,15 +569,13 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
data={projectSnippetsTreeState}
aria-label="project-level-snippets"
nodeRenderer={({ element, ...props }) => {
const isOpened =
isSQLEditorTabsEnabled &&
Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const tabId = createTabId('sql', {
id: element?.metadata?.id as unknown as Snippet['id'],
})
const isPreview = isSQLEditorTabsEnabled && tabs.previewTabId === tabId
const isPreview = tabs.previewTabId === tabId
const isActive = !isPreview && element.metadata?.id === id
const isSelected = selectedSnippets.some((x) => x.id === element.metadata?.id)
@@ -656,15 +651,13 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
data={favoritesTreeState}
aria-label="favorite-snippets"
nodeRenderer={({ element, ...props }) => {
const isOpened =
isSQLEditorTabsEnabled &&
Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const tabId = createTabId('sql', {
id: element?.metadata?.id as unknown as Snippet['id'],
})
const isPreview = isSQLEditorTabsEnabled && tabs.previewTabId === tabId
const isPreview = tabs.previewTabId === tabId
const isActive = !isPreview && element.metadata?.id === id
const isSelected = selectedSnippets.some((x) => x.id === element.metadata?.id)
@@ -746,15 +739,13 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
}}
expandedIds={expandedFolderIds}
nodeRenderer={({ element, ...props }) => {
const isOpened =
isSQLEditorTabsEnabled &&
Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const tabId = createTabId('sql', {
id: element?.metadata?.id as unknown as Snippet['id'],
})
const isPreview = isSQLEditorTabsEnabled && tabs.previewTabId === tabId
const isPreview = tabs.previewTabId === tabId
const isActive = !isPreview && element.metadata?.id === id
const isSelected = selectedSnippets.some((x) => x.id === element.metadata?.id)
@@ -1,7 +1,6 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { IS_PLATFORM } from 'common'
import { useParams } from 'common/hooks/useParams'
import { useIsSQLEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useSQLSnippetFolderContentsQuery } from 'data/content/sql-folder-contents-query'
import { Snippet } from 'data/content/sql-folders-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
@@ -83,7 +82,6 @@ export const SQLEditorTreeViewItem = ({
const { profile } = useProfile()
const { className, onClick } = getNodeProps()
const snapV2 = useSqlEditorV2StateSnapshot()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const isOwner = profile?.id === element?.metadata.owner_id
const isSharedSnippet = element.metadata.visibility === 'project'
@@ -262,13 +260,12 @@ export const SQLEditorTreeViewItem = ({
<ContextMenuItem_Shadcn_
asChild
className="gap-x-2"
onSelect={() => {}}
onFocusCapture={(e) => e.stopPropagation()}
>
<Link
href={`/project/${projectRef}/sql/${element.id}`}
target={isSQLEditorTabsEnabled ? '_self' : '_blank'}
target="_self"
rel="noreferrer"
href={`/project/${projectRef}/sql/${element.id}`}
>
<ExternalLink size={14} />
Open in new tab
@@ -10,7 +10,6 @@ import {
MAX_EXPORT_ROW_COUNT_MESSAGE,
} from 'components/grid/components/header/Header'
import { parseSupaTable } from 'components/grid/SupabaseGrid.utils'
import { useIsTableEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import {
formatTableRowsToSQL,
getEntityLintDetails,
@@ -70,11 +69,9 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({
const snap = useTableEditorStateSnapshot()
const { selectedSchema } = useQuerySchemaState()
// For tabs preview flag logic
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const tabId = createTabId(entity.type, { id: entity.id })
const tabs = useTabsStateSnapshot()
const isPreview = isTableEditorTabsEnabled ? tabs.previewTabId === tabId : false
const isPreview = tabs.previewTabId === tabId
const isOpened = Object.values(tabs.tabsMap).some((tab) => tab.metadata?.tableId === entity.id)
const isActive = Number(id) === entity.id
@@ -5,7 +5,6 @@ import { useEffect, useMemo, useState } from 'react'
import { useParams } from 'common'
import { useBreakpoint } from 'common/hooks/useBreakpoint'
import { useIsTableEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { ProtectedSchemaModal } from 'components/interfaces/Database/ProtectedSchemaWarning'
import EditorMenuListSkeleton from 'components/layouts/TableEditorLayout/EditorMenuListSkeleton'
import AlertError from 'components/ui/AlertError'
@@ -45,11 +44,10 @@ import EntityListItem from './EntityListItem'
import { TableMenuEmptyState } from './TableMenuEmptyState'
const TableEditorMenu = () => {
const { ref, id: _id } = useParams()
const { id: _id } = useParams()
const id = _id ? Number(_id) : undefined
const snap = useTableEditorStateSnapshot()
const { selectedSchema, setSelectedSchema } = useQuerySchemaState()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const isMobile = useBreakpoint()
const [showModal, setShowModal] = useState(false)
@@ -118,10 +116,10 @@ const TableEditorMenu = () => {
const tableEditorTabsCleanUp = useTableEditorTabsCleanUp()
useEffect(() => {
// Clean up tabs + recent items for any tables that might have been removed outside of the dashboard session
if (isTableEditorTabsEnabled && entityTypes && !searchText) {
if (entityTypes && !searchText) {
tableEditorTabsCleanUp({ schemas: [selectedSchema], entities: entityTypes })
}
}, [entityTypes, isTableEditorTabsEnabled, searchText, selectedSchema, tableEditorTabsCleanUp])
}, [entityTypes, searchText, selectedSchema, tableEditorTabsCleanUp])
return (
<>
@@ -6,7 +6,6 @@ import { useRouter } from 'next/router'
import { toast } from 'sonner'
import { useParams } from 'common'
import { TabsUpdateCallout } from 'components/interfaces/App/FeaturePreview/TableEditorTabs'
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries'
import { createSqlSnippetSkeletonV2 } from 'components/interfaces/SQLEditor/SQLEditor.utils'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
@@ -114,7 +113,6 @@ export function NewTab() {
return (
<div className="bg-surface-100 h-full overflow-y-auto py-12">
<div className="mx-auto max-w-2xl flex flex-col gap-10 px-10">
<TabsUpdateCallout />
<div className="grid grid-cols-2 gap-4">
{actions.map((item, i) => (
<ActionCard key={`action-card-${i}`} {...item} />
+34 -39
View File
@@ -12,7 +12,6 @@ import { Plus, X } from 'lucide-react'
import { useRouter } from 'next/router'
import { useParams } from 'common'
import { TabsUpdateTooltip } from 'components/interfaces/App/FeaturePreview/TableEditorTabs'
import { useAppStateSnapshot } from 'state/app-state'
import { editorEntityTypes, useTabsStateSnapshot, type Tab } from 'state/tabs'
import {
@@ -173,43 +172,41 @@ export const EditorTabs = () => {
{/* Non-draggable new tab */}
{hasNewTab && (
<>
<TabsTrigger_Shadcn_
value="new"
className={cn(
'flex items-center gap-2 px-3 text-xs',
'bg-dash-sidebar/50 dark:bg-surface-100/50',
'data-[state=active]:bg-dash-sidebar dark:data-[state=active]:bg-surface-100',
'relative group h-full border-t-2 !border-b-0',
'hover:bg-surface-300 dark:hover:bg-surface-100'
)}
<TabsTrigger_Shadcn_
value="new"
className={cn(
'flex items-center gap-2 px-3 text-xs',
'bg-dash-sidebar/50 dark:bg-surface-100/50',
'data-[state=active]:bg-dash-sidebar dark:data-[state=active]:bg-surface-100',
'relative group h-full border-t-2 !border-b-0',
'hover:bg-surface-300 dark:hover:bg-surface-100'
)}
>
<Plus size={16} strokeWidth={1.5} className={'text-foreground-lighter'} />
<div className="flex items-center gap-0">
<span>New</span>
</div>
<span
role="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
}}
className="ml-1 opacity-0 group-hover:opacity-100 hover:bg-200 rounded-sm cursor-pointer"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
handleClose('new')
}}
>
<Plus size={16} strokeWidth={1.5} className={'text-foreground-lighter'} />
<div className="flex items-center gap-0">
<span>New</span>
</div>
<span
role="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
}}
className="ml-1 opacity-0 group-hover:opacity-100 hover:bg-200 rounded-sm cursor-pointer"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
handleClose('new')
}}
>
<X size={12} className="text-foreground-light" />
</span>{' '}
<div className="absolute w-full -bottom-[1px] left-0 right-0 h-px bg-dash-sidebar dark:bg-surface-100 opacity-0 group-data-[state=active]:opacity-100" />
</TabsTrigger_Shadcn_>
</>
<X size={12} className="text-foreground-light" />
</span>{' '}
<div className="absolute w-full -bottom-[1px] left-0 right-0 h-px bg-dash-sidebar dark:bg-surface-100 opacity-0 group-data-[state=active]:opacity-100" />
</TabsTrigger_Shadcn_>
)}
<AnimatePresence initial={false}>
@@ -235,8 +232,6 @@ export const EditorTabs = () => {
</AnimatePresence>
<div className="grow h-full border-b pr-6" />
</TabsList_Shadcn_>
<TabsUpdateTooltip />
</Tabs_Shadcn_>
<DragOverlay dropAnimation={null}>
@@ -2,10 +2,6 @@ import { usePathname } from 'next/navigation'
import { ComponentProps, ReactNode } from 'react'
import { useParams } from 'common'
import {
useIsSQLEditorTabsEnabled,
useIsTableEditorTabsEnabled,
} from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useTabsStateSnapshot } from 'state/tabs'
import { cn } from 'ui'
import { ProjectLayoutWithAuth } from '../ProjectLayout/ProjectLayout'
@@ -26,12 +22,6 @@ export const EditorBaseLayout = ({ children, title, product, ...props }: Explore
const editor = useEditorType()
const tabs = useTabsStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const tableEditorTabsEnabled = editor === 'table' && isTableEditorTabsEnabled
const sqlEditorTabsEnabled = editor === 'sql' && isSQLEditorTabsEnabled
const hasNoOpenTabs =
editor === 'table' ? tabs.openTabs.filter((x) => !x.startsWith('sql')).length === 0 : false
const hideTabs =
@@ -40,16 +30,14 @@ export const EditorBaseLayout = ({ children, title, product, ...props }: Explore
return (
<ProjectLayoutWithAuth resizableSidebar title={title} product={product} {...props}>
<div className="flex flex-col h-full">
{tableEditorTabsEnabled || sqlEditorTabsEnabled ? (
<div
className={cn(
'h-10 flex items-center',
!hideTabs ? 'bg-surface-200 dark:bg-alternative' : 'bg-surface-100'
)}
>
{hideTabs ? <CollapseButton hideTabs={hideTabs} /> : <EditorTabs />}
</div>
) : null}
<div
className={cn(
'h-10 flex items-center',
!hideTabs ? 'bg-surface-200 dark:bg-alternative' : 'bg-surface-100'
)}
>
{hideTabs ? <CollapseButton hideTabs={hideTabs} /> : <EditorTabs />}
</div>
<div className="h-full">{children}</div>
</div>
</ProjectLayoutWithAuth>
@@ -1,7 +1,6 @@
import { useEffect } from 'react'
import { useParams } from 'common'
import { useIsTableEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { TableGridEditor } from 'components/interfaces/TableGridEditor/TableGridEditor'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
@@ -30,11 +29,9 @@ const TableEditorPage: NextPageWithLayout = () => {
* - selectedTable changes (when a new table is loaded)
* - id changes (when URL parameter changes)
*/
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
useEffect(() => {
// tabs preview flag logic
if (isTableEditorTabsEnabled && selectedTable && projectRef) {
if (selectedTable && projectRef) {
const tabId = createTabId(selectedTable.entity_type, { id: selectedTable.id })
if (!store.tabsMap[tabId]) {
store.addTab({
@@ -52,7 +49,7 @@ const TableEditorPage: NextPageWithLayout = () => {
store.makeTabActive(tabId)
}
}
}, [selectedTable, id, projectRef, isTableEditorTabsEnabled])
}, [selectedTable, id, projectRef])
return <TableGridEditor isLoadingSelectedTable={isLoading} selectedTable={selectedTable} />
}
@@ -1,14 +1,13 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useParams } from 'common'
import { useIsTableEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import EmptyState from 'components/interfaces/TableGridEditor/EmptyState'
import SidePanelEditor from 'components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
import TableEditorLayout from 'components/layouts/TableEditorLayout/TableEditorLayout'
import TableEditorMenu from 'components/layouts/TableEditorLayout/TableEditorMenu'
import { NewTab } from 'components/layouts/Tabs/NewTab'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useAppStateSnapshot } from 'state/app-state'
import { editorEntityTypes, useTabsStateSnapshot } from 'state/tabs'
import type { NextPageWithLayout } from 'types'
@@ -18,7 +17,6 @@ const TableEditorPage: NextPageWithLayout = () => {
const { ref: projectRef } = useParams()
const tabStore = useTabsStateSnapshot()
const appSnap = useAppStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const onTableCreated = (table: { id: number }) => {
router.push(`/project/${projectRef}/editor/${table.id}`)
@@ -26,25 +24,22 @@ const TableEditorPage: NextPageWithLayout = () => {
useEffect(() => {
const lastOpenedTab = appSnap.dashboardHistory.editor
if (isTableEditorTabsEnabled) {
const lastTabId = tabStore.openTabs.find((id) =>
editorEntityTypes.table.includes(tabStore.tabsMap[id]?.type)
)
if (lastOpenedTab !== undefined) {
router.push(`/project/${projectRef}/editor/${appSnap.dashboardHistory.editor}`)
} else if (lastTabId) {
// Handle redirect to last opened table tab, or last table tab
const lastTab = tabStore.tabsMap[lastTabId]
if (lastTab) router.push(`/project/${projectRef}/editor/${lastTab.metadata?.tableId}`)
}
} else if (lastOpenedTab) {
const lastTabId = tabStore.openTabs.find((id) =>
editorEntityTypes.table.includes(tabStore.tabsMap[id]?.type)
)
if (lastOpenedTab !== undefined) {
router.push(`/project/${projectRef}/editor/${appSnap.dashboardHistory.editor}`)
} else if (lastTabId) {
// Handle redirect to last opened table tab, or last table tab
const lastTab = tabStore.tabsMap[lastTabId]
if (lastTab) router.push(`/project/${projectRef}/editor/${lastTab.metadata?.tableId}`)
}
}, [isTableEditorTabsEnabled])
}, [])
return (
<>
{isTableEditorTabsEnabled ? <NewTab /> : <EmptyState />}
<NewTab />
<SidePanelEditor onTableCreated={onTableCreated} />
</>
)
+34 -42
View File
@@ -2,7 +2,6 @@ import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useParams } from 'common/hooks/useParams'
import { useIsSQLEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { SQLEditor } from 'components/interfaces/SQLEditor/SQLEditor'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
@@ -26,7 +25,6 @@ const SqlEditor: NextPageWithLayout = () => {
const tabs = useTabsStateSnapshot()
const appSnap = useAppStateSnapshot()
const snapV2 = useSqlEditorV2StateSnapshot()
const isSqlEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const allSnippets = useSnippets(ref!)
const snippet = allSnippets.find((x) => x.id === id)
@@ -72,23 +70,21 @@ const SqlEditor: NextPageWithLayout = () => {
// Watch for route changes
useEffect(() => {
if (isSqlEditorTabsEnabled) {
if (!router.isReady || !id || id === 'new') return
if (!router.isReady || !id || id === 'new') return
const tabId = createTabId('sql', { id })
const snippet = allSnippets.find((x) => x.id === id)
const tabId = createTabId('sql', { id })
const snippet = allSnippets.find((x) => x.id === id)
tabs.addTab({
id: tabId,
type: 'sql',
label: snippet?.name || 'Untitled Query',
metadata: {
sqlId: id,
name: snippet?.name,
},
})
}
}, [router.isReady, id, isSqlEditorTabsEnabled])
tabs.addTab({
id: tabId,
type: 'sql',
label: snippet?.name || 'Untitled Query',
metadata: {
sqlId: id,
name: snippet?.name,
},
})
}, [router.isReady, id])
if (snippetMissing || invalidId) {
return (
@@ -99,31 +95,27 @@ const SqlEditor: NextPageWithLayout = () => {
title={`Unable to find snippet with ID ${id}`}
description="This snippet doesn't exist in your project"
>
{isSqlEditorTabsEnabled && (
<>
{!!tabId ? (
<Button
type="default"
className="mt-2"
onClick={() => {
tabs.handleTabClose({
id: tabId,
router,
editor,
onClearDashboardHistory: () => {
if (ref) appSnap.setDashboardHistory(ref, 'sql', undefined)
},
})
}}
>
Close tab
</Button>
) : (
<Button asChild type="default" className="mt-2">
<Link href={`/project/${ref}/sql`}>Head back</Link>
</Button>
)}
</>
{!!tabId ? (
<Button
type="default"
className="mt-2"
onClick={() => {
tabs.handleTabClose({
id: tabId,
router,
editor,
onClearDashboardHistory: () => {
if (ref) appSnap.setDashboardHistory(ref, 'sql', undefined)
},
})
}}
>
Close tab
</Button>
) : (
<Button asChild type="default" className="mt-2">
<Link href={`/project/${ref}/sql`}>Head back</Link>
</Button>
)}
</Admonition>
</div>
+13 -19
View File
@@ -1,12 +1,12 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useParams } from 'common'
import { useIsSQLEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
import SQLEditorLayout from 'components/layouts/SQLEditorLayout/SQLEditorLayout'
import { SQLEditorMenu } from 'components/layouts/SQLEditorLayout/SQLEditorMenu'
import { NewTab } from 'components/layouts/Tabs/NewTab'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useAppStateSnapshot } from 'state/app-state'
import { useTabsStateSnapshot } from 'state/tabs'
import type { NextPageWithLayout } from 'types'
@@ -16,26 +16,20 @@ const TableEditorPage: NextPageWithLayout = () => {
const { ref: projectRef } = useParams()
const store = useTabsStateSnapshot()
const appSnap = useAppStateSnapshot()
const isSqlEditorTabsEnabled = useIsSQLEditorTabsEnabled()
useEffect(() => {
if (!isSqlEditorTabsEnabled) {
// Redirect to /new if not using tabs
router.push(`/project/${projectRef}/sql/new`)
} else {
// Handle redirect to last opened snippet tab, or last snippet tab
const lastOpenedTab = appSnap.dashboardHistory.sql
const lastTabId = store.openTabs.find((id) => store.tabsMap[id]?.type === 'sql')
if (lastOpenedTab !== undefined) {
router.push(`/project/${projectRef}/sql/${appSnap.dashboardHistory.sql}`)
} else if (lastTabId) {
const lastTab = store.tabsMap[lastTabId]
if (lastTab) router.push(`/project/${projectRef}/sql/${lastTab.id.replace('sql-', '')}`)
}
// Handle redirect to last opened snippet tab, or last snippet tab
const lastOpenedTab = appSnap.dashboardHistory.sql
const lastTabId = store.openTabs.find((id) => store.tabsMap[id]?.type === 'sql')
if (lastOpenedTab !== undefined) {
router.push(`/project/${projectRef}/sql/${appSnap.dashboardHistory.sql}`)
} else if (lastTabId) {
const lastTab = store.tabsMap[lastTabId]
if (lastTab) router.push(`/project/${projectRef}/sql/${lastTab.id.replace('sql-', '')}`)
}
}, [isSqlEditorTabsEnabled])
}, [])
return isSqlEditorTabsEnabled ? <NewTab /> : null
return <NewTab />
}
TableEditorPage.getLayout = (page) => (
@@ -2,7 +2,6 @@ import { useParams } from 'next/navigation'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useIsSQLEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import SQLQuickstarts from 'components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
@@ -16,25 +15,20 @@ const SqlQuickstarts: NextPageWithLayout = () => {
const { ref } = useParams<{ ref: string }>()
const tabs = useTabsStateSnapshot()
const isSqlEditorTabsEnabled = useIsSQLEditorTabsEnabled()
useEffect(() => {
if (isSqlEditorTabsEnabled) {
if (!router.isReady) return
if (!router.isReady) return
const tabId = createTabId('sql', { id: 'quickstarts' })
tabs.addTab({
id: tabId,
type: 'sql',
label: 'Quickstarts',
metadata: {
sqlId: 'quickstarts',
name: 'quickstarts',
},
})
}
}, [router.isReady, isSqlEditorTabsEnabled, ref])
const tabId = createTabId('sql', { id: 'quickstarts' })
tabs.addTab({
id: tabId,
type: 'sql',
label: 'Quickstarts',
metadata: {
sqlId: 'quickstarts',
name: 'quickstarts',
},
})
}, [router.isReady, ref])
return <SQLQuickstarts />
}
@@ -2,7 +2,6 @@ import { useParams } from 'next/navigation'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useIsSQLEditorTabsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import SQLTemplates from 'components/interfaces/SQLEditor/SQLTemplates/SQLTemplates'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
@@ -16,25 +15,20 @@ const SqlTemplates: NextPageWithLayout = () => {
const { ref } = useParams<{ ref: string }>()
const tabs = useTabsStateSnapshot()
const isSqlEditorTabsEnabled = useIsSQLEditorTabsEnabled()
useEffect(() => {
if (isSqlEditorTabsEnabled) {
if (!router.isReady) return
if (!router.isReady) return
const tabId = createTabId('sql', { id: 'templates' })
tabs.addTab({
id: tabId,
type: 'sql',
label: 'Templates',
metadata: {
sqlId: 'templates',
name: 'templates',
},
})
}
}, [router.isReady, isSqlEditorTabsEnabled, ref])
const tabId = createTabId('sql', { id: 'templates' })
tabs.addTab({
id: tabId,
type: 'sql',
label: 'Templates',
metadata: {
sqlId: 'templates',
name: 'templates',
},
})
}, [router.isReady, ref])
return <SQLTemplates />
}
+2 -2
View File
@@ -1,11 +1,11 @@
import { expect } from '@playwright/test'
import { env } from '../env.config'
import { test } from '../utils/test'
import { toUrl } from '../utils/to-url'
import { env } from '../env.config'
test.describe('SQL Editor', () => {
test('should check if SQL editor can run simple commands', async ({ page }) => {
await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql`))
await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql/new?skip=true`))
const editor = page.getByRole('code').nth(0)