screenshot-to-code/frontend/src/components/CodeMirror.tsx
2023-11-28 09:49:57 -05:00

82 lines
2.1 KiB
TypeScript

import { useRef, useEffect, useMemo } from "react";
import { EditorState } from "@codemirror/state";
import { EditorView, keymap, lineNumbers, ViewUpdate } from "@codemirror/view";
import { espresso, cobalt } from "thememirror";
import {
defaultKeymap,
history,
indentWithTab,
redo,
undo,
} from "@codemirror/commands";
import { bracketMatching } from "@codemirror/language";
import { html } from "@codemirror/lang-html";
import { EditorTheme } from "@/types";
interface Props {
code: string;
editorTheme: EditorTheme;
onCodeChange: (code: string) => void;
}
function CodeMirror({ code, editorTheme, onCodeChange }: Props) {
const ref = useRef<HTMLDivElement>(null);
const view = useRef<EditorView | null>(null);
const editorState = useMemo(
() =>
EditorState.create({
extensions: [
history(),
keymap.of([
...defaultKeymap,
indentWithTab,
{ key: "Mod-z", run: undo, preventDefault: true },
{ key: "Mod-Shift-z", run: redo, preventDefault: true },
]),
lineNumbers(),
bracketMatching(),
html(),
editorTheme === EditorTheme.ESPRESSO ? espresso : cobalt,
EditorView.lineWrapping,
EditorView.updateListener.of((update: ViewUpdate) => {
if (update.docChanged) {
const updatedCode = update.state.doc.toString();
onCodeChange(updatedCode);
}
}),
],
}),
[editorTheme]
);
useEffect(() => {
view.current = new EditorView({
state: editorState,
parent: ref.current as Element,
});
return () => {
if (view.current) {
view.current.destroy();
view.current = null;
}
};
}, []);
useEffect(() => {
if (view.current && view.current.state.doc.toString() !== code) {
view.current.dispatch({
changes: { from: 0, to: view.current.state.doc.length, insert: code },
});
}
}, [code]);
return (
<div
className="overflow-x-scroll overflow-y-scroll mx-2 border-[4px] border-black rounded-[20px]"
ref={ref}
/>
);
}
export default CodeMirror;