// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { useHeight } from "@/app/hook/useHeight"; import loader from "@monaco-editor/loader"; import { Editor, Monaco } from "@monaco-editor/react"; import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; import { useEffect, useRef } from "react"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import "./codeeditor.less"; // there is a global monaco variable (TODO get the correct TS type) declare var monaco: Monaco; function loadMonaco() { loader.config({ paths: { vs: "monaco" } }); loader .init() .then(() => { monaco.editor.defineTheme("wave-theme-dark", { base: "hc-black", inherit: true, rules: [], colors: { "editor.background": "#000000", }, }); monaco.editor.defineTheme("wave-theme-light", { base: "hc-light", inherit: true, rules: [], colors: { "editor.background": "#fefefe", }, }); }) .catch((e) => { console.error("error loading monaco", e); }); } // TODO: need to update these on theme change (pull from CSS vars) document.addEventListener("DOMContentLoaded", () => { setTimeout(loadMonaco, 30); }); function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions { const opts: MonacoTypes.editor.IEditorOptions = { scrollBeyondLastLine: false, fontSize: 12, fontFamily: "Hack", smoothScrolling: true, scrollbar: { useShadows: false, verticalScrollbarSize: 5, horizontalScrollbarSize: 5, }, }; return opts; } interface CodeEditorProps { parentRef: React.MutableRefObject; text: string; filename: string; readonly: boolean; language?: string; onChange?: (text: string) => void; onSave?: () => void; onCancel?: () => void; onEdit?: () => void; } export function CodeEditor({ readonly = false, parentRef, text, language, filename, onChange, onSave, onCancel, onEdit, }: CodeEditorProps) { const divRef = useRef(null); const parentHeight = useHeight(parentRef); const theme = "wave-theme-dark"; useEffect(() => { function handleKeyDown(e: KeyboardEvent) { const waveEvent = adaptFromReactOrNativeKeyEvent(e); if (onSave) { if (checkKeyPressed(waveEvent, "Cmd:s")) { e.preventDefault(); onSave(); return; } } if (onCancel) { if (checkKeyPressed(waveEvent, "Cmd:r")) { e.preventDefault(); onCancel(); return; } } if (onEdit) { if (checkKeyPressed(waveEvent, "Cmd:e")) { e.preventDefault(); onEdit(); return; } } } const currentParentRef = parentRef.current; currentParentRef.addEventListener("keydown", handleKeyDown); return () => { currentParentRef.removeEventListener("keydown", handleKeyDown); }; }, [onSave, onCancel, onEdit]); function handleEditorChange(text: string, ev: MonacoTypes.editor.IModelContentChangedEvent) { if (onChange) { onChange(text); } } function handleEditorOnMount(editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) { // bind Cmd:e editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyE, () => { if (onEdit) { onEdit(); } }); // bind Cmd:s editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { if (onSave) { onSave(); } }); // bind Cmd:r editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, () => { if (onCancel) { onCancel(); } }); } const editorOpts = defaultEditorOptions(); editorOpts.readOnly = readonly; return (
); }