Home / Function/ TextEditor() — supabase Function Reference

TextEditor() — supabase Function Reference

Architecture documentation for the TextEditor() function in TextEditor.tsx from the supabase codebase.

Entity Profile

Relationship Graph

Source Code

apps/studio/components/grid/components/editor/TextEditor.tsx lines 21–218

export const TextEditor = <TRow, TSummaryRow = unknown>({
  row,
  column,
  isNullable,
  isEditable,
  onRowChange,
  onExpandEditor,
}: RenderEditCellProps<TRow, TSummaryRow> & {
  isNullable?: boolean
  isEditable?: boolean
  onExpandEditor: (column: string, row: TRow) => void
}) => {
  const { id: _id } = useParams()
  const id = _id ? Number(_id) : undefined
  const { data: project } = useSelectedProjectQuery()

  const { data: selectedTable } = useTableEditorQuery({
    projectRef: project?.ref,
    connectionString: project?.connectionString,
    id,
  })

  const rawValue = row[column.key as keyof TRow] as unknown
  const initialValue = rawValue || rawValue === '' ? String(rawValue) : null
  const [isPopoverOpen, setIsPopoverOpen] = useState(true)
  const [value, setValue] = useState<string | null>(initialValue)
  const [isConfirmNextModalOpen, setIsConfirmNextModalOpen] = useState(false)
  const isQueueOperationsEnabled = useIsQueueOperationsEnabled()

  const { mutate: getCellValue, isPending, isSuccess } = useGetCellValueMutation()

  const isTruncated = isValueTruncated(initialValue)

  const loadFullValue = () => {
    if (selectedTable === undefined || project === undefined || !isTableLike(selectedTable)) return
    if (selectedTable.primary_keys.length === 0) {
      return toast('Unable to load value as table has no primary keys')
    }

    const pkMatch = selectedTable.primary_keys.reduce((a, b) => {
      return { ...a, [b.name]: (row as any)[b.name] }
    }, {})

    getCellValue(
      {
        table: { schema: selectedTable.schema, name: selectedTable.name },
        column: column.name as string,
        pkMatch,
        projectRef: project?.ref,
        connectionString: project?.connectionString,
      },
      { onSuccess: (data) => setValue(data) }
    )
  }

  const cancelChanges = useCallback(() => {
    if (isEditable) onRowChange(row, true)
    setIsPopoverOpen(false)
  }, [])

  const saveChanges = useCallback(
    (newValue: string | null) => {
      if (isEditable && newValue !== value) {
        onRowChange({ ...row, [column.key]: newValue }, true)
      }
      setIsPopoverOpen(false)
    },
    [isSuccess]
  )

  const onSelectExpand = () => {
    cancelChanges()
    onExpandEditor(column.key, {
      ...row,
      [column.key]: value || (row as any)[column.key],
    })
  }

  const onChange = (_value: string | undefined) => {
    if (!isEditable) return
    if (!_value) setValue('')
    else setValue(_value)
  }

  return (
    <>
      <Popover
        open={isPopoverOpen}
        side="bottom"
        align="start"
        sideOffset={-35}
        className="rounded-none"
        overlay={
          isTruncated && !isSuccess ? (
            <div
              style={{ width: `${column.width}px` }}
              className="flex items-center justify-center flex-col relative"
            >
              <MonacoEditor
                readOnly
                onChange={() => {}}
                width={`${column.width}px`}
                value={value ?? ''}
                language="markdown"
              />
              <TruncatedWarningOverlay isLoading={isPending} loadFullValue={loadFullValue} />
            </div>
          ) : (
            <BlockKeys
              value={value}
              onEscape={cancelChanges}
              onEnter={saveChanges}
              ignoreOutsideClicks={isConfirmNextModalOpen}
            >
              <MonacoEditor
                width={`${column.width}px`}
                value={value ?? ''}
                readOnly={!isEditable}
                onChange={onChange}
              />
              {isEditable && (
                <div className="flex items-start justify-between p-2 bg-surface-200 space-x-2">
                  <div className="space-y-1">
                    <div className="flex items-center space-x-2">
                      <div className="px-1.5 py-[2.5px] rounded bg-surface-300 border border-strong flex items-center justify-center">
                        <span className="text-[10px]">⏎</span>
                      </div>
                      <p className="text-xs text-foreground-light">Save changes</p>
                    </div>
                    <div className="flex items-center space-x-2">
                      <div className="px-1 py-[2.5px] rounded bg-surface-300 border border-strong flex items-center justify-center">
                        <span className="text-[10px]">Esc</span>
                      </div>
                      <p className="text-xs text-foreground-light">Cancel changes</p>
                    </div>
                  </div>
                  <div className="flex flex-col items-end gap-y-1">
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <Button
                          type="default"
                          className="px-1"
                          onClick={() => onSelectExpand()}
                          icon={<Maximize size={12} strokeWidth={2} />}
                        />
                      </TooltipTrigger>
                      <TooltipContent side="bottom">Expand editor</TooltipContent>
                    </Tooltip>
                    {isNullable && (
                      <Button
                        size="tiny"
                        type="default"
                        htmlType="button"
                        onClick={() => {
                          if (isQueueOperationsEnabled) {
                            // Skip confirmation when queue mode is enabled - changes can be reviewed/cancelled
                            saveChanges(null)
                          } else {
                            setIsConfirmNextModalOpen(true)
                          }
                        }}
                      >
                        Set to NULL
                      </Button>
                    )}
                  </div>
                </div>
              )}
            </BlockKeys>
          )
        }
      >
        <div
          className={cn(
            !!value && value.toString().trim().length === 0 && 'sb-grid-fill-container',
            'sb-grid-text-editor__trigger'
          )}
          onClick={() => setIsPopoverOpen(!isPopoverOpen)}
        >
          {value === null ? <NullValue /> : value === '' ? <EmptyValue /> : value}
        </div>
      </Popover>
      <ConfirmationModal
        visible={isConfirmNextModalOpen}
        title="Confirm setting value to NULL"
        confirmLabel="Confirm"
        onCancel={() => setIsConfirmNextModalOpen(false)}
        onConfirm={() => {
          saveChanges(null)
        }}
      >
        <p className="text-sm text-foreground-light">
          Are you sure you wish to set this value to NULL? This action cannot be undone.
        </p>
      </ConfirmationModal>
    </>
  )
}

Subdomains

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free