mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
77c5a0b9d9
## Context Related to Explorer / Notebooks - adds a barebones query cell that minimally can run SQL queries + render results The intention is to migrate the components used in the SQL Editor into this new Query cell since all the functionality is very similar, but the SQL Editor component is tightly coupled to the SQL Editor valtio store. So we'll be duplicating a bit of UI for now - which will also make deprecating the SQL Editor eventually a bit easier by just deleting them Have deliberately omitted a lot of details for now just to keep the PRs small, so will be continuing to build out the QueryCell's functionality in subsequent PRs. This includes - Source selector - Data display (Table / Chart) - Autolimit logic Other changes also includes - Updating NotebookEditor to use the new Explorer UI components that Saxon introduced <img width="500" alt="image" src="https://github.com/user-attachments/assets/d709f6f1-f6cc-4e6a-babd-f5b68bba2a55" /> <img width="500" alt="image" src="https://github.com/user-attachments/assets/a286ceca-ac84-4fbd-afd5-b24f940e2e29" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added notebook database cells for writing, editing, and running SQL queries. * Added query result displays with loading, empty, error, row-limit, and result states. * Added editable notebook titles with save and cancel controls. * New notebooks can start with customizable Markdown and SQL cells. * Added helpful SQL error actions, including copying messages, database connection guidance, and AI Assistant support where available. * **Improvements** * Improved notebook spacing, section layout, toolbar tooltips, and empty-result presentation. * Markdown changes now save automatically through the notebook editor. * Improved drag-and-drop controls and query visibility management. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
175 lines
6.5 KiB
TypeScript
175 lines
6.5 KiB
TypeScript
import {
|
|
DndContext,
|
|
DragEndEvent,
|
|
KeyboardSensor,
|
|
PointerSensor,
|
|
useSensor,
|
|
useSensors,
|
|
} from '@dnd-kit/core'
|
|
import {
|
|
arrayMove,
|
|
SortableContext,
|
|
sortableKeyboardCoordinates,
|
|
verticalListSortingStrategy,
|
|
} from '@dnd-kit/sortable'
|
|
import { useFlag, useParams } from 'common'
|
|
import dayjs from 'dayjs'
|
|
import { useEffect, useRef } from 'react'
|
|
import { cn } from 'ui'
|
|
|
|
import { AdvisorSection } from './AdvisorSection'
|
|
import { ConnectSection } from './ConnectSection'
|
|
import { CustomReportSection } from './CustomReportSection'
|
|
import { DEFAULT_SECTION_ORDER, mergeSectionOrder } from './Home.utils'
|
|
import { ProjectUsageSection } from './ProjectUsageSection'
|
|
import { ProjectUsageSectionDeltas } from './ProjectUsageSectionDeltas'
|
|
import { TopSection } from '@/components/interfaces/ProjectHome/TopSection'
|
|
import { ProjectNeedsSecuring } from '@/components/layouts/ProjectNeedsSecuring/ProjectNeedsSecuring'
|
|
import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
|
|
import { SortableSection } from '@/components/ui/SortableSection'
|
|
import { useLocalStorage } from '@/hooks/misc/useLocalStorage'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { IS_PLATFORM, PROJECT_STATUS } from '@/lib/constants'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
import { useAppStateSnapshot } from '@/state/app-state'
|
|
|
|
const SORT_GRIP_CLASS = 'absolute -left-10'
|
|
|
|
export const ProjectHome = () => {
|
|
const { enableBranching } = useParams()
|
|
const snap = useAppStateSnapshot()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const track = useTrack()
|
|
|
|
const showHomepageUsageDeltas = useFlag('newHomepageUsageDeltas')
|
|
|
|
const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day'))
|
|
|
|
const hasShownEnableBranchingModalRef = useRef(false)
|
|
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
|
|
const isComingUp = project?.status === PROJECT_STATUS.COMING_UP
|
|
|
|
const [sectionOrder, setSectionOrder] = useLocalStorage<string[]>(
|
|
`home-section-order-${project?.ref || 'default'}`,
|
|
DEFAULT_SECTION_ORDER
|
|
)
|
|
|
|
const UsageSection = showHomepageUsageDeltas ? ProjectUsageSectionDeltas : ProjectUsageSection
|
|
|
|
const sensors = useSensors(
|
|
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
)
|
|
|
|
const handleDragEnd = (event: DragEndEvent) => {
|
|
const { active, over } = event
|
|
if (!over || active.id === over.id) return
|
|
setSectionOrder((items) => {
|
|
const oldIndex = items.indexOf(String(active.id))
|
|
const newIndex = items.indexOf(String(over.id))
|
|
if (oldIndex === -1 || newIndex === -1) return items
|
|
|
|
track('home_section_rows_moved', {
|
|
section_moved: String(active.id),
|
|
old_position: oldIndex,
|
|
new_position: newIndex,
|
|
})
|
|
|
|
return arrayMove(items, oldIndex, newIndex)
|
|
})
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (enableBranching && !hasShownEnableBranchingModalRef.current) {
|
|
hasShownEnableBranchingModalRef.current = true
|
|
snap.setShowCreateBranchModal(true)
|
|
}
|
|
}, [enableBranching, snap])
|
|
|
|
useEffect(() => {
|
|
setSectionOrder(mergeSectionOrder)
|
|
}, [setSectionOrder])
|
|
|
|
// On self-hosted the project's `inserted_at` is hard-coded to a past date
|
|
// (so it always looks "mature"), and the home page is otherwise sparse —
|
|
// always surface the Get Connected pane there. Platform keeps the
|
|
// maturity gate so long-running projects don't see it forever.
|
|
const showConnectSection = !!project && (!IS_PLATFORM || !isMatureProject)
|
|
|
|
const renderOrder = mergeSectionOrder(sectionOrder).filter((id) => {
|
|
if (id === 'connect') return showConnectSection
|
|
if (id === 'usage' || id === 'custom-report') return IS_PLATFORM
|
|
return true
|
|
})
|
|
|
|
return (
|
|
<ProjectNeedsSecuring>
|
|
<div className="w-full h-full">
|
|
<ScaffoldContainer size="large" className={cn(isPaused && 'h-full')}>
|
|
<ScaffoldSection
|
|
isFullWidth
|
|
className={cn(isPaused ? 'h-full flex justify-center p-0!' : 'pb-0')}
|
|
>
|
|
<TopSection />
|
|
</ScaffoldSection>
|
|
</ScaffoldContainer>
|
|
{!isPaused && (
|
|
<ScaffoldContainer size="large">
|
|
<ScaffoldSection isFullWidth className="gap-12 pb-32">
|
|
<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
|
|
<SortableContext items={renderOrder} strategy={verticalListSortingStrategy}>
|
|
{renderOrder.map((id) => {
|
|
if (IS_PLATFORM && id === 'usage') {
|
|
return (
|
|
<div
|
|
key={id}
|
|
className={cn(isComingUp && 'opacity-60 pointer-events-none')}
|
|
>
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} id={id}>
|
|
<UsageSection />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
if (id === 'connect' && showConnectSection) {
|
|
return (
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} key={id} id={id}>
|
|
<ConnectSection />
|
|
</SortableSection>
|
|
)
|
|
}
|
|
if (id === 'advisor') {
|
|
return (
|
|
<div
|
|
key={id}
|
|
className={cn(isComingUp && 'opacity-60 pointer-events-none')}
|
|
>
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} id={id}>
|
|
<AdvisorSection showEmptyState={isComingUp} />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
if (IS_PLATFORM && id === 'custom-report') {
|
|
return (
|
|
<div
|
|
key={id}
|
|
className={cn(isComingUp && 'opacity-60 pointer-events-none')}
|
|
>
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} id={id}>
|
|
<CustomReportSection />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
})}
|
|
</SortableContext>
|
|
</DndContext>
|
|
</ScaffoldSection>
|
|
</ScaffoldContainer>
|
|
)}
|
|
</div>
|
|
</ProjectNeedsSecuring>
|
|
)
|
|
}
|