This cell has no content
+ )}Move up
+Move down
+Remove cell
+{(result?.rows ?? []).length.toLocaleString()} rows
-·
-Limit {rowLimit} rows
+ {rowLimit && ( + <> +·
+Limit {rowLimit} rows
+ > + )} ) diff --git a/apps/studio/components/interfaces/Explorer/QueryTab.tsx b/apps/studio/components/interfaces/Explorer/QueryTab.tsx index 432aba351d50c..e6e4774cddfc3 100644 --- a/apps/studio/components/interfaces/Explorer/QueryTab.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryTab.tsx @@ -75,6 +75,7 @@ export const QueryTab = () => { variant="viewport" title={draft.name} sql={draft.uncheckedSql} + source={draft.source} result={result} rowLimit={QUERY_ROW_LIMIT} onTitleChange={(value) => { @@ -83,6 +84,7 @@ export const QueryTab = () => { tabs.updateTab(createTabId('query', { id }), { label: name }) }} onSqlChange={(sql) => explorerQueryState.updateDraft({ id, sql })} + onSourceChange={(source) => explorerQueryState.updateDraft({ id, source })} onResultChange={handleResultChange} /> ) diff --git a/apps/studio/components/interfaces/Explorer/hooks.ts b/apps/studio/components/interfaces/Explorer/hooks.ts index 99e21109f2a6b..506236d2cf26b 100644 --- a/apps/studio/components/interfaces/Explorer/hooks.ts +++ b/apps/studio/components/interfaces/Explorer/hooks.ts @@ -1,6 +1,6 @@ -import { untrustedSql } from '@supabase/pg-meta' import { useRouter } from 'next/router' +import { createMarkdownCellSkeleton, createQueryCellSkeleton } from './utils' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { generateUuid } from '@/lib/api/snippets.browser' import { useProfile } from '@/lib/profile' @@ -23,35 +23,28 @@ export const useCreateNotebook = () => { if (!profile) return console.error('Profile is required') if (!project) return console.error('Project is required') - // [Joshen] Just adding sample data to play around with, keep for now - clean up at the end - const DEFAULT_CELLS = [ - { - _tag: 'markdown_cell', - id: generateUuid(), - text: ` + const sampleMdCell1 = createMarkdownCellSkeleton({ + content: ` # Title A brief description on what this notebook is about - `.trim(), - }, - { - _tag: 'markdown_cell', - id: generateUuid(), - text: ` +`.trim(), + }) + const sampleMdCell2 = createMarkdownCellSkeleton({ + content: ` ## Section This is a sample paragraph to demonstrate the Markdown cells 1. List item 1 2. List item 2 3. List item 3 - `, - }, - { - _tag: 'database_cell', - id: generateUuid(), - view: 'table', - chart: undefined, - unchecked_sql: untrustedSql('select * from colors;'), - row_limit: 100, - }, +`.trim(), + }) + const sampleQueryCell = createQueryCellSkeleton({ sql: 'select * from colors;' }) + + // [Joshen] Just adding sample data to play around with, keep for now - clean up at the end + const DEFAULT_CELLS = [ + sampleMdCell1, + sampleMdCell2, + sampleQueryCell, ] as Notebooks.Content['cells'] const id = idOverride ?? generateUuid() diff --git a/apps/studio/components/interfaces/Explorer/utils.ts b/apps/studio/components/interfaces/Explorer/utils.ts new file mode 100644 index 0000000000000..059c84784d444 --- /dev/null +++ b/apps/studio/components/interfaces/Explorer/utils.ts @@ -0,0 +1,31 @@ +import { untrustedSql } from '@supabase/pg-meta' + +import { generateUuid } from '@/lib/api/snippets.browser' + +export const createQueryCellSkeleton = ({ sql }: { sql?: string } = {}) => { + return { + _tag: 'database_cell' as const, + id: generateUuid(), + view: 'table' as const, + chart: undefined, + unchecked_sql: untrustedSql(sql ?? ''), + row_limit: 100, + } +} + +const DEFAULT_MARKDOWN_CONTENT = ` + # New section + Add notes about your queries and results +`.trim() + +export const createMarkdownCellSkeleton = ({ + content = DEFAULT_MARKDOWN_CONTENT, +}: { + content?: string +} = {}) => { + return { + _tag: 'markdown_cell' as const, + id: generateUuid(), + text: content, + } +} diff --git a/apps/studio/components/ui/SortableSection.tsx b/apps/studio/components/ui/SortableSection.tsx index 3484e61e49eb3..53e4425679511 100644 --- a/apps/studio/components/ui/SortableSection.tsx +++ b/apps/studio/components/ui/SortableSection.tsx @@ -1,17 +1,44 @@ +import { useDndMonitor } from '@dnd-kit/core' import { useSortable } from '@dnd-kit/sortable' import { GripVertical } from 'lucide-react' -import type { CSSProperties, PropsWithChildren } from 'react' -import { Button, cn } from 'ui' +import type { CSSProperties, PropsWithChildren, ReactNode } from 'react' +import { useEffect, useRef, useState } from 'react' +import { Button, cn, DropdownMenu, DropdownMenuTrigger } from 'ui' export const SortableSection = ({ id, children, + actions, gripClassName, -}: PropsWithChildren<{ id: string; gripClassName?: string }>) => { + gripDropdownContent, +}: PropsWithChildren<{ + id: string + gripClassName?: string + actions?: ReactNode + gripDropdownContent?: ReactNode +}>) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id, }) + const [menuOpen, setMenuOpen] = useState(false) + const isDraggingRef = useRef(false) + const openTimeoutRef = useRef