add supainput

This commit is contained in:
Jordi Enric committed 2024-07-26 11:14:13 +02:00
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)
},
})