daedalOS/components/apps/MonacoEditor/useMonaco.ts
2023-11-18 21:02:03 -08:00

148 lines
4.5 KiB
TypeScript

import { basename, dirname } from "path";
import { useCallback, useEffect, useState } from "react";
import { loader } from "@monaco-editor/react";
import type * as Monaco from "monaco-editor/esm/vs/editor/editor.api";
import {
URL_DELIMITER,
config,
theme,
} from "components/apps/MonacoEditor/config";
import {
detectLanguage,
getSaveFileInfo,
relocateShadowRoot,
} from "components/apps/MonacoEditor/functions";
import { type Model } from "components/apps/MonacoEditor/types";
import { type ContainerHookProps } from "components/system/Apps/AppContainer";
import useTitle from "components/system/Window/useTitle";
import { useFileSystem } from "contexts/fileSystem";
import { useProcesses } from "contexts/process";
import {
DEFAULT_TEXT_FILE_SAVE_PATH,
MILLISECONDS_IN_SECOND,
} from "utils/constants";
import { getExtension } from "utils/functions";
import { lockGlobal, unlockGlobal } from "utils/globals";
const useMonaco = ({
containerRef,
id,
setLoading,
url,
}: ContainerHookProps): void => {
const { readFile, updateFolder, writeFile } = useFileSystem();
const { argument: setArgument } = useProcesses();
const { prependFileToTitle } = useTitle(id);
const [editor, setEditor] = useState<Monaco.editor.IStandaloneCodeEditor>();
const [monaco, setMonaco] = useState<typeof Monaco>();
const createModelUri = useCallback(
(modelUrl: string, instance = 0): Monaco.Uri | undefined => {
const uriName = `${modelUrl}${URL_DELIMITER}${instance}`;
const models = monaco?.editor.getModels();
return models?.some(
(model) => (model as Model)._associatedResource.path === uriName
)
? createModelUri(modelUrl, instance + 1)
: monaco?.Uri.parse(uriName);
},
[monaco?.Uri, monaco?.editor]
);
const createModel = useCallback(async () => {
const newModel = monaco?.editor.createModel(
(await readFile(url)).toString(),
detectLanguage(getExtension(url)),
createModelUri(url)
);
newModel?.onDidChangeContent(() => prependFileToTitle(basename(url), true));
return newModel as Monaco.editor.ITextModel;
}, [createModelUri, monaco?.editor, prependFileToTitle, readFile, url]);
const loadFile = useCallback(async () => {
if (monaco && editor && url.startsWith("/")) {
unlockGlobal("define");
editor.getModel()?.dispose();
editor.setModel(await createModel());
window.setTimeout(
() => lockGlobal("define"),
2.5 * MILLISECONDS_IN_SECOND
);
}
prependFileToTitle(basename(url || DEFAULT_TEXT_FILE_SAVE_PATH));
}, [createModel, editor, monaco, prependFileToTitle, url]);
useEffect(() => {
if (!monaco) {
unlockGlobal("define");
loader.config(config);
loader.init().then((monacoInstance) => {
lockGlobal("define");
setMonaco(monacoInstance);
});
}
}, [monaco]);
useEffect(() => {
editor?.onKeyDown(async (event) => {
const { ctrlKey, code, keyCode } = event;
// Q: Is it 83 or 49?
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
if (ctrlKey && (code === "KeyS" || keyCode === 83)) {
event.preventDefault();
const [saveUrl, saveData] = getSaveFileInfo(url, editor);
if (saveUrl && typeof saveData === "string") {
await writeFile(saveUrl, saveData, true);
updateFolder(dirname(saveUrl), basename(saveUrl));
prependFileToTitle(basename(saveUrl));
}
}
});
}, [editor, prependFileToTitle, updateFolder, url, writeFile]);
useEffect(() => {
if (monaco && !editor && containerRef.current) {
const currentEditor = monaco.editor.create(containerRef.current, {
automaticLayout: true,
theme,
});
containerRef.current
?.closest("section")
?.addEventListener("focus", () => currentEditor.focus(), {
passive: true,
});
containerRef.current?.addEventListener("blur", relocateShadowRoot, {
capture: true,
passive: true,
});
setEditor(currentEditor);
setArgument(id, "editor", currentEditor);
setLoading(false);
}
return () => {
if (editor && monaco) {
editor.getModel()?.dispose();
editor.dispose();
lockGlobal("define");
}
};
}, [containerRef, editor, id, monaco, setArgument, setLoading]);
useEffect(() => {
if (monaco && editor && url) {
loadFile();
}
}, [editor, loadFile, monaco, url]);
};
export default useMonaco;