123 lines
2.9 KiB
TypeScript
123 lines
2.9 KiB
TypeScript
import { json as jsonLang } from "@codemirror/lang-json";
|
|
import {
|
|
EditorView,
|
|
TransactionSpec,
|
|
useCodeMirror,
|
|
ViewUpdate,
|
|
} from "@uiw/react-codemirror";
|
|
import { useRef, useEffect } from "react";
|
|
import { useJsonDoc } from "~/hooks/useJsonDoc";
|
|
import { getEditorSetup } from "~/utilities/codeMirrorSetup";
|
|
import { darkTheme, lightTheme } from "~/utilities/codeMirrorTheme";
|
|
import { useTheme } from "./ThemeProvider";
|
|
import { useHotkeys } from "react-hotkeys-hook";
|
|
|
|
export type CodeEditorProps = {
|
|
content: string;
|
|
language?: "json";
|
|
readOnly?: boolean;
|
|
onChange?: (value: string) => void;
|
|
onUpdate?: (update: ViewUpdate) => void;
|
|
selection?: { start: number; end: number };
|
|
};
|
|
|
|
const languages = {
|
|
json: jsonLang,
|
|
};
|
|
|
|
type CodeEditorDefaultProps = Required<
|
|
Omit<CodeEditorProps, "content" | "onChange" | "onUpdate">
|
|
>;
|
|
|
|
const defaultProps: CodeEditorDefaultProps = {
|
|
language: "json",
|
|
readOnly: true,
|
|
selection: { start: 0, end: 0 },
|
|
};
|
|
|
|
export function CodeEditor(opts: CodeEditorProps) {
|
|
const { content, language, readOnly, onChange, onUpdate, selection } = {
|
|
...defaultProps,
|
|
...opts,
|
|
};
|
|
|
|
const [theme] = useTheme();
|
|
|
|
const extensions = getEditorSetup();
|
|
|
|
const languageExtension = languages[language];
|
|
|
|
extensions.push(languageExtension());
|
|
|
|
const editor = useRef(null);
|
|
const { setContainer, view, state } = useCodeMirror({
|
|
container: editor.current,
|
|
extensions,
|
|
editable: !readOnly,
|
|
contentEditable: !readOnly,
|
|
value: content,
|
|
autoFocus: false,
|
|
theme: theme === "light" ? lightTheme() : darkTheme(),
|
|
indentWithTab: false,
|
|
basicSetup: false,
|
|
onChange,
|
|
onUpdate,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (editor.current) {
|
|
setContainer(editor.current);
|
|
}
|
|
}, [editor.current]);
|
|
|
|
const setSelectionRef = useRef(false);
|
|
|
|
useEffect(() => {
|
|
if (setSelectionRef.current) {
|
|
return;
|
|
}
|
|
|
|
if (view) {
|
|
setSelectionRef.current = true;
|
|
|
|
const selectionStart = selection?.start ?? defaultProps.selection.start;
|
|
const selectionEnd = selection?.end ?? defaultProps.selection.end;
|
|
|
|
const lineNumber = state?.doc.lineAt(selectionStart).number;
|
|
|
|
const transactionSpec: TransactionSpec = {
|
|
selection: { anchor: selectionStart, head: selectionEnd },
|
|
effects: EditorView.scrollIntoView(selectionStart, {
|
|
y: "start",
|
|
yMargin: 100,
|
|
}),
|
|
};
|
|
|
|
view.dispatch(transactionSpec);
|
|
}
|
|
}, [selection, view, setSelectionRef.current]);
|
|
|
|
const { minimal } = useJsonDoc();
|
|
|
|
useHotkeys(
|
|
"ctrl+a,meta+a,command+a",
|
|
(e) => {
|
|
e.preventDefault();
|
|
view?.dispatch({ selection: { anchor: 0, head: state?.doc.length } });
|
|
},
|
|
[view, state]
|
|
);
|
|
|
|
|
|
return (
|
|
<div>
|
|
<div
|
|
className={`${
|
|
minimal ? "h-jsonViewerHeightMinimal" : "h-jsonViewerHeight"
|
|
} overflow-y-auto no-scrollbar`}
|
|
ref={editor}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|