'use client'; import { ScrollArea } from "@/components/ui/scroll-area"; import { useKeymapExtension } from "@/hooks/useKeymapExtension"; import { useThemeNormalized } from "@/hooks/useThemeNormalized"; import { json, jsonLanguage, jsonParseLinter } from "@codemirror/lang-json"; import { linter } from "@codemirror/lint"; import { EditorView, hoverTooltip } from "@codemirror/view"; import CodeMirror, { ReactCodeMirrorRef } from "@uiw/react-codemirror"; import { handleRefresh, jsonCompletion, jsonSchemaHover, jsonSchemaLinter, stateExtensions } from "codemirror-json-schema"; import { useRef, forwardRef, useImperativeHandle, Ref, ReactNode } from "react"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { Schema } from "ajv"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; export type QuickActionFn = (previous: T) => T; export type QuickAction = { name: string; fn: QuickActionFn; description?: string | ReactNode; }; interface ConfigEditorProps { value: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any onChange: (...event: any[]) => void; actions: QuickAction[], schema: Schema; } const customAutocompleteStyle = EditorView.baseTheme({ ".cm-tooltip.cm-completionInfo": { padding: "8px", fontSize: "12px", fontFamily: "monospace", }, ".cm-tooltip-hover.cm-tooltip": { padding: "8px", fontSize: "12px", fontFamily: "monospace", } }); export function onQuickAction( action: QuickActionFn, config: string, view: EditorView, options?: { focusEditor?: boolean; moveCursor?: boolean; } ) { const { focusEditor = false, moveCursor = true, } = options ?? {}; let previousConfig: T; try { previousConfig = JSON.parse(config) as T; } catch { return; } const nextConfig = action(previousConfig); const next = JSON.stringify(nextConfig, null, 2); if (focusEditor) { view.focus(); } const cursorPos = next.lastIndexOf(`""`) + 1; view.dispatch({ changes: { from: 0, to: config.length, insert: next, } }); if (moveCursor) { view.dispatch({ selection: { anchor: cursorPos, head: cursorPos } }); } } export const isConfigValidJson = (config: string) => { try { JSON.parse(config); return true; } catch (_e) { return false; } } const ConfigEditor = (props: ConfigEditorProps, forwardedRef: Ref) => { const { value, onChange, actions, schema } = props; const editorRef = useRef(null); useImperativeHandle( forwardedRef, () => editorRef.current as ReactCodeMirrorRef ); const keymapExtension = useKeymapExtension(editorRef.current?.view); const { theme } = useThemeNormalized(); return (
{actions.map(({ name, fn, description }, index) => (
{index !== actions.length - 1 && ( )}
))}
) }; // @see: https://stackoverflow.com/a/78692562 export default forwardRef(ConfigEditor) as ( props: ConfigEditorProps & { ref?: Ref }, ) => ReturnType;