code editor responsiveness and various fixes (#129)

This commit is contained in:
Red J Adaya
2024-07-25 12:00:35 -07:00
committed by GitHub
parent 0e46b79c22
commit 96420ea3ef
6 changed files with 116 additions and 62 deletions
+27
View File
@@ -612,6 +612,32 @@ function makeAppMenu() {
role: "quit",
},
];
const viewMenu: Electron.MenuItemConstructorOptions[] = [
{
role: "forceReload",
},
{
role: "toggleDevTools",
},
{
type: "separator",
},
{
role: "resetZoom",
},
{
role: "zoomIn",
},
{
role: "zoomOut",
},
{
type: "separator",
},
{
role: "togglefullscreen",
},
];
const menuTemplate: Electron.MenuItemConstructorOptions[] = [
{
role: "appMenu",
@@ -626,6 +652,7 @@ function makeAppMenu() {
},
{
role: "viewMenu",
submenu: viewMenu,
},
{
role: "windowMenu",
+1 -8
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
import { useLongClick } from "@/app/hook/useLongClick";
import { CodeEditor } from "@/app/view/codeeditor/codeeditor";
import { Button } from "@/element/button";
import { ErrorBoundary } from "@/element/errorboundary";
import { CenteredDiv } from "@/element/quickelems";
@@ -331,11 +330,7 @@ const BlockFrame_Default_Component = ({
);
} else if (elem.elemtype == "textbutton") {
return (
<Button
key={key}
className={clsx("border-radius-4 vertical-padding-3", elem.className)}
onClick={(e) => elem.onClick(e)}
>
<Button key={key} className={elem.className} onClick={(e) => elem.onClick(e)}>
{elem.text}
</Button>
);
@@ -509,8 +504,6 @@ function getViewElemAndModel(
viewModel = previewModel;
} else if (blockView === "plot") {
viewElem = <PlotView key={blockId} />;
} else if (blockView === "codeedit") {
viewElem = <CodeEditor key={blockId} text={null} filename={null} />;
} else if (blockView === "web") {
const webviewModel = makeWebViewModel(blockId);
viewElem = <WebView key={blockId} parentRef={blockRef} model={webviewModel} />;
+9 -4
View File
@@ -15,7 +15,7 @@
line-height: 1.5;
white-space: nowrap;
user-select: none;
-webkit-user-select: none;
font-weight: 500;
color: var(--main-text-color);
@@ -140,7 +140,12 @@
border-radius: 4px;
}
.vertical-padding-3 {
padding-top: 3px;
padding-bottom: 3px;
.vertical-padding-2 {
padding-top: 2px;
padding-bottom: 2px;
}
.horizontal-padding-10 {
padding-left: 10px;
padding-right: 10px;
}
+2 -2
View File
@@ -31,8 +31,8 @@
rgba(255, 255, 255, 0.1);
box-shadow:
inset 0 1px 0 0 hsla(0, 0%, 100%, 0.05),
0 0 0 0.5px hsla(0, 0%, 100%, 0.35),
inset 0 -1px 0 0 rgba(0, 0, 0, 0.2);
0 0 0 0.5px hsla(0, 0%, 100%, 0.25),
inset 0 0 0 0 rgba(0, 0, 0, 0.2);
}
.name {
+63 -44
View File
@@ -1,18 +1,17 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { globalStore } from "@/store/global";
import { useHeight } from "@/app/hook/useHeight";
import loader from "@monaco-editor/loader";
import { Editor, Monaco } from "@monaco-editor/react";
import * as jotai from "jotai";
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
import { useEffect, useRef, useState } from "react";
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;
let monacoLoadedAtom = jotai.atom(false);
function loadMonaco() {
loader.config({ paths: { vs: "monaco" } });
@@ -35,8 +34,6 @@ function loadMonaco() {
"editor.background": "#fefefe",
},
});
globalStore.set(monacoLoadedAtom, true);
console.log("monaco loaded", monaco);
})
.catch((e) => {
console.error("error loading monaco", e);
@@ -63,47 +60,72 @@ function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions {
return opts;
}
interface CodeEditProps {
readonly?: boolean;
interface CodeEditorProps {
parentRef: React.MutableRefObject<HTMLDivElement>;
text: string;
language?: string;
filename: string;
readonly: boolean;
language?: string;
onChange?: (text: string) => void;
onSave?: () => void;
onCancel?: () => void;
onEdit?: () => void;
}
export function CodeEditor({ readonly = false, text, language, filename, onChange }: CodeEditProps) {
const [divDims, setDivDims] = useState(null);
const monacoLoaded = jotai.useAtomValue(monacoLoadedAtom);
const monacoRef = useRef<MonacoTypes.editor.IStandaloneCodeEditor | null>(null);
export function CodeEditor({
readonly = false,
parentRef,
text,
language,
filename,
onChange,
onSave,
onCancel,
onEdit,
}: CodeEditorProps) {
const divRef = useRef<HTMLDivElement>(null);
const monacoLoadedRef = useRef<boolean | null>(null);
const parentHeight = useHeight(parentRef);
const theme = "wave-theme-dark";
useEffect(() => {
if (!divRef.current) {
return;
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 height = divRef.current.clientHeight;
const width = divRef.current.clientWidth;
setDivDims({ height, width });
}, []);
useEffect(() => {
if (monacoLoadedRef.current === null) {
monacoLoadedRef.current = monacoLoaded;
}
}, [monacoLoaded]);
const currentParentRef = parentRef.current;
currentParentRef.addEventListener("keydown", handleKeyDown);
function handleEditorMount(editor: MonacoTypes.editor.IStandaloneCodeEditor) {
monacoRef.current = editor;
const monacoModel = editor.getModel();
//monaco.editor.setModelLanguage(monacoModel, "text/markdown");
}
return () => {
currentParentRef.removeEventListener("keydown", handleKeyDown);
};
}, [onSave, onCancel, onEdit]);
function handleEditorChange(text: string, ev: MonacoTypes.editor.IModelContentChangedEvent) {
onChange(text);
if (onChange) {
onChange(text);
}
}
const editorOpts = defaultEditorOptions();
@@ -112,18 +134,15 @@ export function CodeEditor({ readonly = false, text, language, filename, onChang
return (
<div className="code-editor-wrapper">
<div className="code-editor" ref={divRef}>
{divDims != null && monacoLoaded ? (
<Editor
theme={theme}
height={divDims.height}
value={text}
onMount={handleEditorMount}
options={editorOpts}
onChange={handleEditorChange}
path={filename}
language={language}
/>
) : null}
<Editor
theme={theme}
height={parentHeight}
value={text}
options={editorOpts}
onChange={handleEditorChange}
path={filename}
language={language}
/>
</div>
</div>
);
+14 -4
View File
@@ -115,13 +115,13 @@ export class PreviewModel implements ViewModel {
{
elemtype: "textbutton",
text: "Save",
className: "primary warning",
className: "primary warning border-radius-4 vertical-padding-2 horizontal-padding-10",
onClick: this.handleFileSave.bind(this),
},
{
elemtype: "textbutton",
text: "Cancel",
className: "secondary",
className: "secondary border-radius-4 vertical-padding-2 horizontal-padding-10",
onClick: () => this.toggleCodeEditorReadOnly(true),
}
);
@@ -129,7 +129,7 @@ export class PreviewModel implements ViewModel {
viewTextChildren.push({
elemtype: "textbutton",
text: "Edit",
className: "secondary",
className: "secondary border-radius-4 vertical-padding-2 horizontal-padding-10",
onClick: () => this.toggleCodeEditorReadOnly(false),
});
}
@@ -388,17 +388,21 @@ function StreamingPreview({ fileInfo }: { fileInfo: FileInfo }) {
}
function CodeEditPreview({
parentRef,
contentAtom,
filename,
readonly,
isCeViewAtom,
newFileContentAtom,
model,
}: {
parentRef: React.MutableRefObject<HTMLDivElement>;
contentAtom: jotai.Atom<Promise<string>>;
filename: string;
readonly: boolean;
isCeViewAtom: jotai.PrimitiveAtom<boolean>;
newFileContentAtom: jotai.PrimitiveAtom<string>;
model: PreviewModel;
}) {
const fileContent = jotai.useAtomValue(contentAtom);
const setIsCeView = jotai.useSetAtom(isCeViewAtom);
@@ -413,10 +417,14 @@ function CodeEditPreview({
return (
<CodeEditor
parentRef={parentRef}
readonly={readonly}
text={fileContent}
filename={filename}
onChange={(text) => setNewFileContent(text)}
onSave={() => model.handleFileSave()}
onCancel={() => model.toggleCodeEditorReadOnly(true)}
onEdit={() => model.toggleCodeEditorReadOnly(false)}
/>
);
}
@@ -519,10 +527,12 @@ function PreviewView({ blockId, model }: { blockId: string; model: PreviewModel
view = (
<CodeEditPreview
readonly={ceReadOnly}
parentRef={contentRef}
contentAtom={fileContentAtom}
filename={fileName}
isCeViewAtom={isCeViewAtom}
newFileContentAtom={newFileContentAtom}
model={model}
/>
);
} else if (mimeType === "directory") {
@@ -553,4 +563,4 @@ function PreviewView({ blockId, model }: { blockId: string; model: PreviewModel
);
}
export { PreviewView, makePreviewModel };
export { makePreviewModel, PreviewView };