| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- import Editor, { Monaco, OnChange, OnMount, useMonaco } from '@monaco-editor/react'
- import { noop } from 'lodash'
- import type { editor } from 'monaco-editor'
- import { MutableRefObject, useEffect, useRef } from 'react'
- import { cn } from 'ui'
- import { Markdown } from '@/components/interfaces/Markdown'
- import { formatSql } from '@/lib/formatSql'
- // [Joshen] Is there a way we can just have one single MonacoEditor component that's shared across the dashboard?
- // Feels like we're creating multiple copies of Editor. I'm keen to make this one the defacto as well so lets make sure
- // this component does not have RLS specific logic
- interface RLSCodeEditorProps {
- id: string
- defaultValue?: string
- onInputChange?: (value?: string) => void
- wrapperClassName?: string
- className?: string
- value?: string
- placeholder?: string
- readOnly?: boolean
- disableTabToUsePlaceholder?: boolean
- lineNumberStart?: number
- onChange?: () => void
- onMount?: () => void
- editorRef: MutableRefObject<editor.IStandaloneCodeEditor | null>
- monacoRef?: MutableRefObject<Monaco>
- }
- export const RLSCodeEditor = ({
- id,
- defaultValue,
- onInputChange,
- wrapperClassName,
- className,
- value,
- placeholder,
- readOnly = false,
- disableTabToUsePlaceholder = false,
- lineNumberStart,
- onChange = noop,
- onMount: _onMount = noop,
- editorRef,
- monacoRef,
- }: RLSCodeEditorProps) => {
- const hasValue = useRef<editor.IContextKey<boolean>>(null)
- const monaco = useMonaco()
- const placeholderId = `monaco-placeholder-${id}`
- const options: editor.IStandaloneEditorConstructionOptions = {
- tabSize: 2,
- fontSize: 13,
- readOnly,
- minimap: { enabled: false },
- wordWrap: 'on' as const,
- contextmenu: true,
- lineNumbers:
- lineNumberStart !== undefined ? (num) => (num + lineNumberStart).toString() : undefined,
- glyphMargin: undefined,
- lineNumbersMinChars: 4,
- folding: undefined,
- scrollBeyondLastLine: false,
- }
- const onMount: OnMount = async (editor, monaco) => {
- editorRef.current = editor
- if (monacoRef !== undefined) monacoRef.current = monaco
- hasValue.current = editor.createContextKey('hasValue', false)
- const placeholderEl = document.getElementById(placeholderId) as HTMLElement | null
- if (placeholderEl && placeholder !== undefined && (value ?? '').trim().length === 0) {
- placeholderEl.style.display = 'block'
- }
- if (!disableTabToUsePlaceholder) {
- editor.addCommand(
- monaco.KeyCode.Tab,
- () => {
- editor.executeEdits('source', [
- {
- // @ts-ignore
- identifier: 'add-placeholder',
- range: new monaco.Range(1, 1, 1, 1),
- text: (placeholder ?? '')
- .split('\n\n')
- .join('\n')
- .replaceAll('*', '')
- .replaceAll(' ', ''),
- },
- ])
- },
- '!hasValue'
- )
- }
- editor.focus()
- _onMount()
- }
- const onChangeContent: OnChange = (value) => {
- if (hasValue.current) {
- hasValue.current.set((value ?? '').length > 0)
- }
- const placeholderEl = document.getElementById(placeholderId) as HTMLElement | null
- if (placeholderEl) {
- if (!value) {
- placeholderEl.style.display = 'block'
- } else {
- placeholderEl.style.display = 'none'
- }
- }
- onChange()
- onInputChange?.(value)
- }
- // when the value has changed, trigger the onChange callback so that the height of the container can be adjusted.
- // Happens when the value wordwraps and is updated via a template.
- useEffect(() => {
- onChange()
- }, [value])
- useEffect(() => {
- if (monaco) {
- // Enable pgsql format
- const formatprovider = monaco.languages.registerDocumentFormattingEditProvider('pgsql', {
- async provideDocumentFormattingEdits(model: any) {
- const value = model.getValue()
- const formatted = formatSql(value)
- return [
- {
- range: model.getFullModelRange(),
- text: formatted.trim(),
- },
- ]
- },
- })
- return () => {
- formatprovider.dispose()
- }
- }
- }, [monaco])
- useEffect(() => {
- if (value !== undefined && value.trim().length > 0) {
- const placeholderEl = document.getElementById(placeholderId) as HTMLElement | null
- if (placeholderEl) placeholderEl.style.display = 'none'
- }
- }, [value])
- return (
- <>
- <Editor
- path={id}
- theme="briven"
- wrapperProps={{ className: cn(wrapperClassName) }}
- className={cn(className, 'monaco-editor')}
- value={value ?? undefined}
- defaultLanguage="pgsql"
- defaultValue={defaultValue ?? undefined}
- options={options}
- onMount={onMount}
- onChange={onChangeContent}
- />
- {placeholder !== undefined && (
- <div
- id={placeholderId}
- className={cn(
- 'monaco-placeholder absolute top-0 left-[57px] text-sm pointer-events-none font-mono tracking-tighter',
- '[&>div>p]:text-foreground-lighter [&>div>p]:m-0!'
- )}
- style={{ display: 'none' }}
- >
- <Markdown content={placeholder} />
- </div>
- )}
- </>
- )
- }
|