mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 07:39:07 +03:00
add supainput
This commit is contained in:
1 parent
f6b8fa0e15
commit
643c3cb8a8
5 files changed
+174
-3
No files matched your search
@@ -1,3 +1,25 @@
|
||||
import { NodeViewProps } from '@tiptap/react'
|
||||
|
||||
export type TiptapNodeViewProps<T> = NodeViewProps & { node: { attrs: T } }
|
||||
|
||||
export const BLOCKS = [
|
||||
{
|
||||
type: 'input',
|
||||
label: 'Input',
|
||||
},
|
||||
{
|
||||
type: 'json',
|
||||
label: 'JSON',
|
||||
},
|
||||
{
|
||||
type: 'table',
|
||||
label: 'Table',
|
||||
},
|
||||
{
|
||||
type: 'chart',
|
||||
label: 'Chart',
|
||||
},
|
||||
] as const
|
||||
|
||||
export type Block = (typeof BLOCKS)[number]
|
||||
export type BlockType = Block['type']
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
BarChart,
|
||||
Code,
|
||||
Database,
|
||||
FileJsonIcon,
|
||||
FormInputIcon,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
@@ -66,6 +68,27 @@ export const SlashCommand = Extension.create({
|
||||
const iconClassNames = 'h-3.5 w-3.5 text-zinc-500'
|
||||
|
||||
const DEFAULT_SLASH_COMMANDS: SlashCommandItem[] = [
|
||||
{
|
||||
title: 'Input',
|
||||
searchTerms: ['input'],
|
||||
icon: <FormInputIcon className={iconClassNames} />,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).insertContent('<supa-input name="" />').run()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'JSON',
|
||||
searchTerms: ['sql', 'json'],
|
||||
icon: <FileJsonIcon className={iconClassNames} />,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.insertContent("<supa-block type='json' sql='SELECT * FROM customers'></sql-table><p></p>")
|
||||
.run()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'SQL Table',
|
||||
searchTerms: ['sql', 'table'],
|
||||
|
||||
@@ -3,34 +3,57 @@ import { Node } from '@tiptap/core'
|
||||
import { ReactNodeViewRenderer, NodeViewProps } from '@tiptap/react'
|
||||
import { useState } from 'react'
|
||||
import { NodeViewWrapper, mergeAttributes } from '@tiptap/react'
|
||||
import { TiptapNodeViewProps } from './shared'
|
||||
import { BlockType, TiptapNodeViewProps } from './shared'
|
||||
import DataGrid, { Column, RenderRowProps, Row } from 'react-data-grid'
|
||||
import BarChart from 'components/ui/Charts/BarChart'
|
||||
import { useParams } from 'common'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
|
||||
import { Button, cn } from 'ui'
|
||||
import { Button, Input_Shadcn_, cn } from 'ui'
|
||||
import CodeEditor from 'components/ui/CodeEditor/CodeEditor'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns'
|
||||
import { RefreshCwIcon, Trash2Icon } from 'lucide-react'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { useRouter } from 'next/router'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
function SQLTableComponent(props: TiptapNodeViewProps<{ sql: string; type: 'table' | 'chart' }>) {
|
||||
function SQLTableComponent(props: TiptapNodeViewProps<{ sql: string; type: BlockType }>) {
|
||||
const {
|
||||
node: {
|
||||
attrs: { sql, type },
|
||||
},
|
||||
} = props
|
||||
|
||||
const router = useRouter()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const [innerSQL, setInnerSQL] = useState(sql)
|
||||
const [source, onSourceChange] = useState<'postgres' | 'logs'>('postgres')
|
||||
const [mode, setMode] = useState<'edit' | 'view'>('edit')
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||
const [inputLabel, setInputLabel] = useState('')
|
||||
|
||||
const { mutate: runQuery, isLoading, data } = useExecuteSqlMutation()
|
||||
|
||||
function execute(sql: string) {
|
||||
// Check if the SQL has any variables (starting with $)
|
||||
const variables = sql.match(/\$\w+/g)
|
||||
const values = variables?.map((variable) => router.query[variable.substring(1)] as string)
|
||||
|
||||
if (variables && !values?.every((value) => value)) {
|
||||
toast.error('Please provide all necessary variables')
|
||||
return
|
||||
}
|
||||
|
||||
if (variables) {
|
||||
// Replace the variables with the values from the URL
|
||||
variables.forEach((variable) => {
|
||||
sql = sql.replace(variable, router.query[variable.substring(1)] as string)
|
||||
})
|
||||
}
|
||||
|
||||
console.log(sql)
|
||||
|
||||
if (project) {
|
||||
runQuery({ sql, projectRef: project.ref, connectionString: project.connectionString })
|
||||
}
|
||||
@@ -83,6 +106,7 @@ function SQLTableComponent(props: TiptapNodeViewProps<{ sql: string; type: 'tabl
|
||||
yAxisKey={columns[1]?.key || ''}
|
||||
/>
|
||||
)}
|
||||
{type === 'json' && <pre>{JSON.stringify(data?.result, null, 2)}</pre>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { SupaBlock } from './supa-block'
|
||||
import { SupaInput } from './supa-input'
|
||||
import GlobalDragHandle from 'tiptap-extension-global-drag-handle'
|
||||
import { SlashCommand, getSlashCommandSuggestions } from './slash-commands'
|
||||
import { TrailingNode } from './trailing-node'
|
||||
@@ -12,6 +13,7 @@ export function SupaEditor() {
|
||||
extensions: [
|
||||
StarterKit,
|
||||
SupaBlock,
|
||||
SupaInput,
|
||||
GlobalDragHandle,
|
||||
SlashCommand.configure({
|
||||
suggestion: getSlashCommandSuggestions([]),
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
'use client'
|
||||
import { Node } from '@tiptap/core'
|
||||
import { ReactNodeViewRenderer, NodeViewProps } from '@tiptap/react'
|
||||
import { useState } from 'react'
|
||||
import { NodeViewWrapper, mergeAttributes } from '@tiptap/react'
|
||||
import { BlockType, TiptapNodeViewProps } from './shared'
|
||||
import DataGrid, { Column, RenderRowProps, Row } from 'react-data-grid'
|
||||
import BarChart from 'components/ui/Charts/BarChart'
|
||||
import { useParams } from 'common'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
|
||||
import { Button, Input_Shadcn_, cn } from 'ui'
|
||||
import CodeEditor from 'components/ui/CodeEditor/CodeEditor'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns'
|
||||
import { RefreshCwIcon, Trash2Icon } from 'lucide-react'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
function SupaInputComponent(props: TiptapNodeViewProps<{ name: string }>) {
|
||||
const {
|
||||
node: {
|
||||
attrs: { name },
|
||||
},
|
||||
} = props
|
||||
|
||||
// This input will have a label and a value, the value will be stored in the URL as a param
|
||||
// Using the label, you can get the value of the input in SQL blocks like this ${label}
|
||||
|
||||
const router = useRouter()
|
||||
const [inputName, setInputName] = useState(name)
|
||||
const [mode, setMode] = useState<'edit' | 'view'>('edit')
|
||||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
{mode === 'edit' ? (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
className="flex flex-col mt-8 group gap-4"
|
||||
>
|
||||
<Label>Input Name</Label>
|
||||
<p>This will be used to reference this input in your SQL queries.</p>
|
||||
<Input_Shadcn_ value={inputName} onChange={(e) => setInputName(e.target.value)} />
|
||||
<Button
|
||||
onClick={() => {
|
||||
props.updateAttributes({ name: inputName })
|
||||
setMode('view')
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Label>{inputName}</Label>
|
||||
<Input_Shadcn_
|
||||
value={router.query[inputName]}
|
||||
onChange={(e) => {
|
||||
// Update URL param
|
||||
router.push({
|
||||
...router,
|
||||
query: {
|
||||
...router.query,
|
||||
[inputName]: e.target.value,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const name = 'supa-input'
|
||||
export const SupaInput = Node.create({
|
||||
name,
|
||||
group: 'block',
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
name: {
|
||||
default: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: name }]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [name, mergeAttributes(HTMLAttributes)]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(SupaInputComponent)
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user