mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +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 -->
124 lines
4.4 KiB
TypeScript
124 lines
4.4 KiB
TypeScript
import { Clipboard, MessageSquare, MoreVertical, Settings, Trash } from 'lucide-react'
|
|
import { useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
copyToClipboard,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from 'ui'
|
|
|
|
import {
|
|
ExplorerToolbar,
|
|
ExplorerToolbarAction,
|
|
ExplorerToolbarActions,
|
|
ExplorerToolbarIcon,
|
|
ExplorerToolbarTitle,
|
|
} from './ExplorerToolbar'
|
|
import { useExplorerDeleteItem } from '@/components/layouts/ExplorerLayout/ExplorerProvider'
|
|
import { AIAssistantMetadataWarning } from '@/components/ui/AIAssistantPanel/AIAssistantMetadataWarning'
|
|
import type { AssistantChatHeaderProps } from '@/components/ui/AIAssistantPanel/AssistantChat'
|
|
import { ShortcutPills } from '@/components/ui/ShortcutTooltip'
|
|
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
|
import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
|
|
|
interface ExplorerChatToolbarProps extends AssistantChatHeaderProps {
|
|
chatId: string
|
|
shortcutsEnabled: boolean
|
|
}
|
|
|
|
export const ExplorerChatToolbar = ({
|
|
chatId,
|
|
shortcutsEnabled,
|
|
isChatLoading,
|
|
showMetadataWarning,
|
|
updatedOptInSinceMCP,
|
|
aiOptInLevel,
|
|
}: ExplorerChatToolbarProps) => {
|
|
const snap = useAiAssistantStateSnapshot()
|
|
const chat = snap.chats[chatId]
|
|
const [isOptInModalOpen, setIsOptInModalOpen] = useState(false)
|
|
const { onSelectDelete } = useExplorerDeleteItem()
|
|
|
|
const handleCopyChatId = () => {
|
|
copyToClipboard(chatId, () => toast.success(`Copied chat ID for ${chat?.name}`))
|
|
}
|
|
|
|
const handleSaveName = (name: string) => {
|
|
if (name.trim()) snap.renameChat(chatId, name.trim())
|
|
}
|
|
|
|
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID, handleCopyChatId, {
|
|
enabled: shortcutsEnabled && !isChatLoading,
|
|
})
|
|
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS, () => setIsOptInModalOpen(true), {
|
|
enabled: shortcutsEnabled && !isChatLoading,
|
|
})
|
|
|
|
return (
|
|
<div className="z-30 sticky top-0">
|
|
<ExplorerToolbar aria-label="Chat toolbar">
|
|
<ExplorerToolbarIcon>
|
|
<MessageSquare size={16} strokeWidth={2} />
|
|
</ExplorerToolbarIcon>
|
|
<ExplorerToolbarTitle onSaveTitle={handleSaveName}>{chat?.name ?? ''}</ExplorerToolbarTitle>
|
|
<ExplorerToolbarActions>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<ExplorerToolbarAction
|
|
aria-label="More options"
|
|
icon={<MoreVertical size={16} strokeWidth={2} />}
|
|
disabled={isChatLoading}
|
|
/>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-60">
|
|
<DropdownMenuItem className="justify-between" onClick={handleCopyChatId}>
|
|
<div className="flex items-center gap-x-2">
|
|
<Clipboard size={14} />
|
|
<span>Copy chat ID</span>
|
|
</div>
|
|
<ShortcutPills
|
|
sequence={SHORTCUT_DEFINITIONS[SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID].sequence}
|
|
/>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
className="justify-between"
|
|
onClick={() => setIsOptInModalOpen(true)}
|
|
>
|
|
<div className="flex items-center gap-x-2">
|
|
<Settings size={14} />
|
|
<span>Permission settings</span>
|
|
</div>
|
|
<ShortcutPills
|
|
sequence={
|
|
SHORTCUT_DEFINITIONS[SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS].sequence
|
|
}
|
|
/>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
className="gap-x-2"
|
|
onClick={() => onSelectDelete({ id: chatId, type: 'chat', name: chat?.name ?? '' })}
|
|
>
|
|
<Trash size={14} />
|
|
<span>Delete chat</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</ExplorerToolbarActions>
|
|
</ExplorerToolbar>
|
|
|
|
<AIAssistantMetadataWarning
|
|
visible={isOptInModalOpen}
|
|
onVisibleChange={setIsOptInModalOpen}
|
|
showMetadataWarning={showMetadataWarning}
|
|
updatedOptInSinceMCP={updatedOptInSinceMCP}
|
|
aiOptInLevel={aiOptInLevel}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|