mirror of
https://github.com/Rohithgilla12/data-peek
synced 2026-04-21 12:57:16 +00:00
feat(notebooks): add Import .dpnb UI in sidebar
- "+" button now opens a menu with "New notebook" and "Import .dpnb…" - File picker accepts .dpnb files, parses and recreates the notebook with all cells and pinned results intact - Fix type narrowing in tab-query-editor (early return for notebook tabs)
This commit is contained in:
parent
3069e60419
commit
cbdbef2043
2 changed files with 85 additions and 12 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { BookOpen, ChevronRight, Plus, MoreHorizontal, Trash2, FolderOpen } from 'lucide-react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { BookOpen, ChevronRight, Plus, MoreHorizontal, Trash2, FolderOpen, Upload } from 'lucide-react'
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
|
|
@ -23,6 +23,7 @@ import {
|
|||
|
||||
import { useNotebookStore } from '@/stores/notebook-store'
|
||||
import { useConnectionStore, useTabStore, notify } from '@/stores'
|
||||
import { parseDpnb } from './notebook-export'
|
||||
import type { Notebook } from '@shared/index'
|
||||
|
||||
export function NotebookSidebar() {
|
||||
|
|
@ -38,6 +39,7 @@ export function NotebookSidebar() {
|
|||
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set())
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitialized) {
|
||||
|
|
@ -65,6 +67,59 @@ export function NotebookSidebar() {
|
|||
notify.success('Notebook deleted', `"${nb.title}" was removed.`)
|
||||
}
|
||||
|
||||
const handleImportClick = () => {
|
||||
if (!activeConnectionId) {
|
||||
notify.error('No connection', 'Connect to a database before importing a notebook.')
|
||||
return
|
||||
}
|
||||
fileInputRef.current?.click()
|
||||
}
|
||||
|
||||
const handleFileSelected = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
e.target.value = ''
|
||||
if (!file || !activeConnectionId) return
|
||||
|
||||
try {
|
||||
const text = await file.text()
|
||||
const parsed = parseDpnb(text)
|
||||
if (!parsed) {
|
||||
notify.error('Invalid file', 'The file is not a valid .dpnb notebook.')
|
||||
return
|
||||
}
|
||||
|
||||
const nb = await createNotebook({
|
||||
title: parsed.title,
|
||||
connectionId: activeConnectionId,
|
||||
folder: parsed.folder
|
||||
})
|
||||
if (!nb) {
|
||||
notify.error('Import failed', 'Could not create the notebook.')
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < parsed.cells.length; i++) {
|
||||
const cell = parsed.cells[i]
|
||||
const addResult = await window.api.notebooks.addCell(nb.id, {
|
||||
type: cell.type,
|
||||
content: cell.content,
|
||||
order: i
|
||||
})
|
||||
if (!addResult.success || !addResult.data) continue
|
||||
if (cell.pinnedResult) {
|
||||
await window.api.notebooks.updateCell(addResult.data.id, {
|
||||
pinnedResult: cell.pinnedResult
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
createNotebookTab(nb.connectionId, nb.id, nb.title)
|
||||
notify.success('Notebook imported', `"${parsed.title}" is ready.`)
|
||||
} catch (err) {
|
||||
notify.error('Import failed', err instanceof Error ? err.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
|
||||
const toggleFolder = (folder: string) => {
|
||||
setExpandedFolders((prev) => {
|
||||
const next = new Set(prev)
|
||||
|
|
@ -130,15 +185,33 @@ export function NotebookSidebar() {
|
|||
/>
|
||||
<span>Notebooks</span>
|
||||
</CollapsibleTrigger>
|
||||
<SidebarGroupAction
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleCreate()
|
||||
}}
|
||||
title="New notebook"
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</SidebarGroupAction>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarGroupAction
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
title="New or import notebook"
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</SidebarGroupAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-48">
|
||||
<DropdownMenuItem onClick={handleCreate}>
|
||||
<Plus className="text-muted-foreground" />
|
||||
<span>New notebook</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleImportClick}>
|
||||
<Upload className="text-muted-foreground" />
|
||||
<span>Import .dpnb…</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".dpnb,application/json"
|
||||
onChange={handleFileSelected}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
</SidebarGroupLabel>
|
||||
<CollapsibleContent>
|
||||
<SidebarGroupContent>
|
||||
|
|
|
|||
|
|
@ -1092,7 +1092,7 @@ export function TabQueryEditor({ tabId }: TabQueryEditorProps) {
|
|||
}
|
||||
}, [handleRunQuery, tab, tabConnection])
|
||||
|
||||
if (!tab) {
|
||||
if (!tab || tab.type === 'notebook') {
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue