mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
### Context Adds context menu to the explorer nav to allow users to delete notebooks or chats from there (instead of having to open the notebook / chat first, then select delete from the header) <img width="304" height="339" alt="image" src="https://github.com/user-attachments/assets/2ad0f21c-0d49-407c-97a2-ef11ae59e280" /> ### Changes involved: - Consolidates the rendering of explorer nav items into a single component that both `ExplorerNavNotebook`, `ExplorerNavHome` and `ExplorerNavChats` use. - We were previously rendering a link for notebook, and a button for chats. But chats' button was just calling `router.push` under the hood. - Opting to use a Context to handle the delete functionality such that we can render the confirmation modal just once at the layout level since there's multiple places that have this delete functionality - notebook tab, chat tab, context menu in each of the explorer navs ### To test - [ ] Verify that navigating around the explorer is status quo - [ ] Verify that you can delete a notebook/chat from within the notebook/chat tab - [ ] Verify that you can delete a notebook/chat from the explorer nav - [ ] Verify that after deleting a notebook/chat from either locations, the tabs should clear <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Unified chat and notebook navigation with direct links and active-item highlighting. - Added context-menu options to delete chats and notebooks with confirmation and status notifications. - Added double-click support for keeping recently viewed chats and notebooks open as tabs. - Deleted items and their related tabs are removed automatically. - **Tests** - Updated Explorer navigation tests for the unified link-based item behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import { useParams } from 'common'
|
|
import { useRouter } from 'next/router'
|
|
import { createContext, useContext, useState, type PropsWithChildren } from 'react'
|
|
import { toast } from 'sonner'
|
|
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
|
|
|
import { useContentDeleteMutation } from '@/data/content/content-delete-mutation'
|
|
import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
|
|
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
|
import { useNotebooksStateSnapshot } from '@/state/notebooks/notebooks-state'
|
|
import { createTabId, useTabsStateSnapshot } from '@/state/tabs'
|
|
|
|
type ExplorerDeleteItem = { id: string; type: 'notebook' | 'chat'; name: string }
|
|
|
|
interface ExplorerContextValue {
|
|
onSelectDelete: (item: ExplorerDeleteItem) => void
|
|
}
|
|
|
|
const ExplorerContext = createContext<ExplorerContextValue | undefined>(undefined)
|
|
|
|
export const useExplorerDeleteItem = () => {
|
|
const context = useContext(ExplorerContext)
|
|
if (!context) {
|
|
throw new Error('useExplorerDeleteItem must be used within an ExplorerProvider')
|
|
}
|
|
return context
|
|
}
|
|
|
|
export const ExplorerProvider = ({ children }: PropsWithChildren) => {
|
|
const router = useRouter()
|
|
const { ref } = useParams()
|
|
const tabs = useTabsStateSnapshot()
|
|
const notebooksSnap = useNotebooksStateSnapshot()
|
|
const aiAssistantSnap = useAiAssistantStateSnapshot()
|
|
const { setLastVisitedExplorerTab } = useDashboardHistory()
|
|
|
|
const [pending, setPending] = useState<ExplorerDeleteItem | null>(null)
|
|
|
|
const onClearDashboardHistory = () => setLastVisitedExplorerTab(undefined)
|
|
|
|
const closeTabForItem = (item: ExplorerDeleteItem) => {
|
|
tabs.handleTabClose({
|
|
id: createTabId(item.type, { id: item.id }),
|
|
router,
|
|
editor: 'explorer',
|
|
onClearDashboardHistory,
|
|
})
|
|
}
|
|
|
|
const { mutate: deleteNotebook, isPending: isDeletingNotebook } = useContentDeleteMutation({
|
|
onSuccess: () => {
|
|
if (!pending) return
|
|
toast.success('Successfully deleted notebook')
|
|
notebooksSnap.removeNotebook({ id: pending.id })
|
|
closeTabForItem(pending)
|
|
setPending(null)
|
|
},
|
|
onError: (error) => toast.error(`Failed to delete notebook: ${error.message}`),
|
|
})
|
|
|
|
const handleConfirm = () => {
|
|
if (!pending) return
|
|
|
|
if (pending.type === 'notebook') {
|
|
if (!ref) return
|
|
deleteNotebook({ projectRef: ref, ids: [pending.id] })
|
|
} else {
|
|
aiAssistantSnap.deleteChat(pending.id)
|
|
toast.success(`Deleted "${pending.name}"`)
|
|
closeTabForItem(pending)
|
|
setPending(null)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ExplorerContext.Provider value={{ onSelectDelete: setPending }}>
|
|
{children}
|
|
|
|
<ConfirmationModal
|
|
size="small"
|
|
variant="destructive"
|
|
visible={!!pending}
|
|
title={`Confirm to delete ${pending?.type} '${pending?.name ?? ''}'`}
|
|
confirmLabel={`Delete ${pending?.type}`}
|
|
confirmLabelLoading="Deleting..."
|
|
loading={isDeletingNotebook}
|
|
onCancel={() => setPending(null)}
|
|
onConfirm={handleConfirm}
|
|
>
|
|
<p className="text-sm text-foreground-light">
|
|
This action cannot be undone. Are you sure you want to delete '{pending?.name}'?
|
|
</p>
|
|
</ConfirmationModal>
|
|
</ExplorerContext.Provider>
|
|
)
|
|
}
|