mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
code editor responsiveness and various fixes (#129)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user