lingo3d
Version:
Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor
286 lines • 12.4 kB
JavaScript
import { jsx as _jsx } from "preact/jsx-runtime";
import { editor, Uri, KeyMod, KeyCode, Range } from "monaco-editor/esm/vs/editor/editor.api";
import { splitFileName } from "@lincode/utils";
import { useRef, useEffect, useState } from "preact/hooks";
import useResizeObserver from "../hooks/useResizeObserver";
const noSuggestion = {
show: false,
suggestions: [],
focused: ""
};
const defaultLanguages = {
js: "javascript",
ts: "typescript",
html: "html",
css: "css",
json: "json"
};
const defaultFiles = { "app.jsx": Promise.resolve("") };
const defaultFile = "app.jsx";
export default () => {
let currentModel = null;
let currentEditor;
let currentSaveAll;
let currentOnSave;
let currentOnUndo;
let currentOnRedo;
let currentFocused = false;
const currentDispose = () => {
currentModel = null;
currentEditor = undefined;
currentSaveAll = undefined;
currentOnSave = undefined;
currentOnUndo = undefined;
currentOnRedo = undefined;
currentFocused = false;
};
const Monaco = ({ style, className, theme, minimap = false, fontSize, language, languages = defaultLanguages, files = defaultFiles, file = defaultFile, onSuggestionChange, onChange, onModelChange, onCursorPositionChange, onScrollChange, onSaveAll, onSave, onUndo, onRedo, onFocus, onBlur, onKeyDown, onContextMenu }) => {
const editorDivRef = useRef(null);
currentOnSave = onSave;
currentOnUndo = onUndo;
currentOnRedo = onRedo;
useEffect(() => {
currentEditor?.updateOptions({
minimap: { enabled: minimap },
fontSize
});
}, [minimap, fontSize]);
useEffect(() => {
currentEditor && theme && editor.setTheme(theme);
}, [theme]);
const programmaticSetValueRef = useRef(false);
useEffect(() => {
const el = editorDivRef.current;
const editorInstance = (currentEditor = editor.create(el, {
theme,
minimap: { enabled: minimap },
fontSize,
language,
contextmenu: false,
tabSize: 2
}));
editorInstance.addCommand(KeyMod.CtrlCmd | KeyCode.KeyS, () => {
const model = editorInstance.getModel();
model && currentOnSave?.(model.getValue(), model.uri.path);
});
editorInstance.addCommand(KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyS, (currentSaveAll = () => {
const entries = [];
if (filesRef.current)
for (const key of Object.keys(filesRef.current)) {
const model = editor.getModel(Uri.parse(key));
model &&
entries.push([model.uri.path, model.getValue()]);
}
return onSaveAll?.(entries);
}));
editorInstance.addCommand(KeyMod.CtrlCmd | KeyCode.Enter, () => {
editorInstance.trigger("monaco", "editor.action.insertLineAfter", undefined);
});
editorInstance.addCommand(KeyMod.CtrlCmd | KeyCode.KeyZ, () => {
currentOnUndo?.();
editorInstance.trigger("monaco", "undo", undefined);
});
editorInstance.addCommand(KeyMod.CtrlCmd | KeyCode.KeyY, () => {
currentOnRedo?.();
editorInstance.trigger("monaco", "redo", undefined);
});
editorInstance.addCommand(KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyZ, () => {
currentOnRedo?.();
editorInstance.trigger("monaco", "redo", undefined);
});
editorInstance.onDidFocusEditorText(() => (currentFocused = true));
editorInstance.onDidBlurEditorText(() => (currentFocused = false));
onFocus && editorInstance.onDidFocusEditorText(onFocus);
onBlur && editorInstance.onDidBlurEditorText(onBlur);
onKeyDown && editorInstance.onKeyDown(onKeyDown);
onContextMenu && editorInstance.onContextMenu(onContextMenu);
onModelChange && editorInstance.onDidChangeModel(onModelChange);
onCursorPositionChange &&
editorInstance.onDidChangeCursorPosition(onCursorPositionChange);
onScrollChange && editorInstance.onDidScrollChange(onScrollChange);
onChange &&
editorInstance.onDidChangeModelContent((e) => {
if (programmaticSetValueRef.current) {
programmaticSetValueRef.current = false;
return;
}
onChange(e);
});
let observer;
if (onSuggestionChange) {
editorInstance.onDidFocusEditorText(() => {
const widgetEl = el.querySelector(".suggest-widget");
if (!widgetEl)
return;
observer?.disconnect();
let suggestionOld = noSuggestion;
observer = new MutationObserver(() => {
let suggestionNew = noSuggestion;
if (widgetEl.classList.contains("visible")) {
const suggestions = [];
let focused = "";
const rows = widgetEl.querySelector(".monaco-list-rows");
if (rows)
for (const item of rows.children) {
const suggestion = item.textContent?.trim();
if (suggestion) {
suggestions.push(suggestion);
item.classList.contains("focused") &&
(focused = suggestion);
}
}
suggestions.length &&
(suggestionNew = {
suggestions,
focused,
show: true
});
}
suggestionOld !== suggestionNew &&
onSuggestionChange(suggestionNew);
suggestionOld = suggestionNew;
});
observer.observe(widgetEl, { attributes: true });
});
}
return () => {
editorInstance.dispose();
observer?.disconnect();
currentDispose();
};
}, []);
const filesRef = useRef(files);
const [viewStateMap] = useState(() => new Map());
useEffect(() => {
if (!files) {
filesRef.current = files;
return;
}
for (const key of Object.keys(filesRef.current ?? {}))
if (!(key in files)) {
setTimeout(() => editor.getModel(Uri.parse(key))?.dispose());
viewStateMap.delete(key);
}
for (const [key, value] of Object.entries(files)) {
const uri = Uri.parse(key);
const model = editor.getModel(uri);
if (model)
continue;
const extension = splitFileName(uri.path)[1];
const newModel = editor.createModel("", languages?.[extension ?? ""] ?? language, uri);
if (typeof value === "string") {
programmaticSetValueRef.current = true;
newModel.setValue(value);
}
else
value.then((s) => {
programmaticSetValueRef.current = true;
newModel.setValue(s);
});
}
filesRef.current = files;
}, [files]);
const fileRef = useRef();
useEffect(() => {
const editorInstance = currentEditor;
fileRef.current &&
viewStateMap.set(fileRef.current, editorInstance.saveViewState());
currentModel = file ? editor.getModel(Uri.parse(file)) : null;
editorInstance.setModel(currentModel);
const viewState = file && viewStateMap.get(file);
viewState && editorInstance.restoreViewState(viewState);
fileRef.current = file;
}, [file]);
const [elRef, size] = useResizeObserver();
useEffect(() => {
currentEditor?.layout();
}, [size]);
return (_jsx("div", { style: style, className: className, ref: elRef, children: _jsx("div", { style: { width: "100%", height: "100%" }, ref: editorDivRef }) }));
};
const decorationMap = new WeakMap();
const setValue = (val) => void currentEditor?.executeEdits("monaco", [
{
range: new Range(1, 1, Infinity, Infinity),
text: val
}
]);
const highlightLine = (line) => {
if (!currentModel)
return;
currentModel.deltaDecorations(decorationMap.get(currentModel) ?? [], []);
decorationMap.set(currentModel, currentModel.deltaDecorations([], [
{
range: new Range(line, 1, line, 1),
options: {
className: "monaco-highlight",
isWholeLine: true
}
}
]));
currentEditor?.revealLineInCenter(line);
};
const controls = {
highlightLine,
clearHighlight: () => currentModel?.deltaDecorations(decorationMap.get(currentModel) ?? [], []),
replace: (lineStart, columnStart, lineEnd, columnEnd, text) => {
currentModel?.pushStackElement();
currentEditor?.executeEdits("monaco", [
{
range: new Range(lineStart, columnStart, lineEnd, columnEnd),
text
}
]);
},
insertLine: (line, text) => {
currentModel?.pushStackElement();
const col = currentModel?.getLineLength(line) ?? 0;
currentEditor?.executeEdits("monaco", [
{
range: new Range(line, col + 1, line, col + 1),
text: "\n" + text,
forceMoveMarkers: true
}
]);
},
getEditor: () => currentEditor,
getModel: () => currentModel,
getValue: () => currentModel?.getValue() ?? "",
getSelection: () => currentEditor?.getSelection(),
setSelection: (selection) => void currentEditor?.setSelection(selection),
setScrollTop: (val) => currentEditor?.setScrollTop(val),
setScrollLeft: (val) => currentEditor?.setScrollLeft(val),
setValue,
clearValue: () => setValue(""),
getURI: () => currentModel?.uri.path ?? "",
saveAll: () => currentSaveAll?.() || Promise.resolve(),
save: () => {
currentModel &&
currentOnSave?.(currentModel.getValue(), currentModel.uri.path);
currentEditor?.focus();
},
newLine: () => {
currentEditor?.trigger("monaco", "editor.action.insertLineAfter", undefined);
currentEditor?.focus();
},
undo: () => {
currentOnUndo?.();
currentEditor?.trigger("monaco", "undo", undefined);
currentEditor?.focus();
},
redo: () => {
currentOnRedo?.();
currentEditor?.trigger("monaco", "redo", undefined);
currentEditor?.focus();
},
focus: () => void currentEditor?.focus(),
suggest: () => void currentEditor?.trigger("monaco", "editor.action.triggerSuggest", undefined),
selectNextSuggestion: () => void currentEditor?.trigger("monaco", "selectNextSuggestion", undefined),
focused: () => currentFocused,
jumpTo: (uri, line, fileOpen) => {
fileOpen(uri);
setTimeout(() => highlightLine(line));
}
};
return { Monaco, controls };
};
//# sourceMappingURL=makeMonaco.js.map