mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Keep changes in preview after save (#360)
A bit hacky, but should allow preview to update to latest file contents after save
This commit is contained in:
parent
04fb8e5aad
commit
13ac6af4d0
@ -178,7 +178,7 @@ type MarkdownProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts, onClickExecute }: MarkdownProps) => {
|
const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts, onClickExecute }: MarkdownProps) => {
|
||||||
const textAtomValue = useAtomValueSafe(textAtom);
|
const textAtomValue = useAtomValueSafe<string>(textAtom);
|
||||||
const tocRef = useRef<TocItem[]>([]);
|
const tocRef = useRef<TocItem[]>([]);
|
||||||
const showToc = useAtomValueSafe(showTocAtom) ?? false;
|
const showToc = useAtomValueSafe(showTocAtom) ?? false;
|
||||||
const contentsOsRef = useRef<OverlayScrollbarsComponentRef>(null);
|
const contentsOsRef = useRef<OverlayScrollbarsComponentRef>(null);
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import { atoms } from "@/app/store/global";
|
import { atoms } from "@/app/store/global";
|
||||||
|
import { useAtomValueSafe } from "@/util/util";
|
||||||
import loader from "@monaco-editor/loader";
|
import loader from "@monaco-editor/loader";
|
||||||
import { Editor, Monaco } from "@monaco-editor/react";
|
import { Editor, Monaco } from "@monaco-editor/react";
|
||||||
import { atom, useAtomValue } from "jotai";
|
import { Atom, atom, useAtomValue } from "jotai";
|
||||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||||
import React, { useMemo, useRef } from "react";
|
import React, { useMemo, useRef, useState } from "react";
|
||||||
import "./codeeditor.less";
|
import "./codeeditor.less";
|
||||||
|
|
||||||
// there is a global monaco variable (TODO get the correct TS type)
|
// there is a global monaco variable (TODO get the correct TS type)
|
||||||
@ -70,7 +71,8 @@ function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface CodeEditorProps {
|
interface CodeEditorProps {
|
||||||
text: string;
|
text?: string;
|
||||||
|
textAtom?: Atom<string> | Atom<Promise<string>>;
|
||||||
filename: string;
|
filename: string;
|
||||||
language?: string;
|
language?: string;
|
||||||
onChange?: (text: string) => void;
|
onChange?: (text: string) => void;
|
||||||
@ -87,11 +89,13 @@ const stickyScrollEnabledAtom = atom((get) => {
|
|||||||
return settings["editor:stickyscrollenabled"] ?? false;
|
return settings["editor:stickyscrollenabled"] ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function CodeEditor({ text, language, filename, onChange, onMount }: CodeEditorProps) {
|
export function CodeEditor({ text, textAtom, language, filename, onChange, onMount }: CodeEditorProps) {
|
||||||
const divRef = useRef<HTMLDivElement>(null);
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
const unmountRef = useRef<() => void>(null);
|
const unmountRef = useRef<() => void>(null);
|
||||||
const minimapEnabled = useAtomValue(minimapEnabledAtom);
|
const minimapEnabled = useAtomValue(minimapEnabledAtom);
|
||||||
const stickyScrollEnabled = useAtomValue(stickyScrollEnabledAtom);
|
const stickyScrollEnabled = useAtomValue(stickyScrollEnabledAtom);
|
||||||
|
const textAtomValue = useAtomValueSafe<string>(textAtom);
|
||||||
|
const [textValue] = useState(() => textAtomValue ?? text);
|
||||||
const theme = "wave-theme-dark";
|
const theme = "wave-theme-dark";
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -127,7 +131,7 @@ export function CodeEditor({ text, language, filename, onChange, onMount }: Code
|
|||||||
<div className="code-editor" ref={divRef}>
|
<div className="code-editor" ref={divRef}>
|
||||||
<Editor
|
<Editor
|
||||||
theme={theme}
|
theme={theme}
|
||||||
value={text}
|
value={textValue}
|
||||||
options={editorOpts}
|
options={editorOpts}
|
||||||
onChange={handleEditorChange}
|
onChange={handleEditorChange}
|
||||||
onMount={handleEditorOnMount}
|
onMount={handleEditorOnMount}
|
||||||
|
@ -126,7 +126,9 @@ export class PreviewModel implements ViewModel {
|
|||||||
fileMimeType: jotai.Atom<Promise<string>>;
|
fileMimeType: jotai.Atom<Promise<string>>;
|
||||||
fileMimeTypeLoadable: jotai.Atom<Loadable<string>>;
|
fileMimeTypeLoadable: jotai.Atom<Loadable<string>>;
|
||||||
fileContent: jotai.Atom<Promise<string>>;
|
fileContent: jotai.Atom<Promise<string>>;
|
||||||
newFileContent: jotai.PrimitiveAtom<string | null>;
|
fileContentLastSaved: jotai.PrimitiveAtom<string>;
|
||||||
|
fileContentModified: jotai.PrimitiveAtom<string>;
|
||||||
|
previewFileContent: jotai.WritableAtom<Promise<string>, [value: string], void>;
|
||||||
|
|
||||||
openFileModal: jotai.PrimitiveAtom<boolean>;
|
openFileModal: jotai.PrimitiveAtom<boolean>;
|
||||||
openFileError: jotai.PrimitiveAtom<string>;
|
openFileError: jotai.PrimitiveAtom<string>;
|
||||||
@ -208,6 +210,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
const blockData = get(this.blockAtom);
|
const blockData = get(this.blockAtom);
|
||||||
return blockData?.meta?.edit ?? false;
|
return blockData?.meta?.edit ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.viewName = jotai.atom("Preview");
|
this.viewName = jotai.atom("Preview");
|
||||||
this.viewText = jotai.atom((get) => {
|
this.viewText = jotai.atom((get) => {
|
||||||
let headerPath = get(this.metaFilePath);
|
let headerPath = get(this.metaFilePath);
|
||||||
@ -241,7 +244,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
let saveClassName = "secondary";
|
let saveClassName = "secondary";
|
||||||
if (get(this.newFileContent) !== null) {
|
if (get(this.fileContentModified) !== null) {
|
||||||
saveClassName = "primary";
|
saveClassName = "primary";
|
||||||
}
|
}
|
||||||
if (isCeView) {
|
if (isCeView) {
|
||||||
@ -368,7 +371,6 @@ export class PreviewModel implements ViewModel {
|
|||||||
return fileInfo?.mimetype;
|
return fileInfo?.mimetype;
|
||||||
});
|
});
|
||||||
this.fileMimeTypeLoadable = loadable(this.fileMimeType);
|
this.fileMimeTypeLoadable = loadable(this.fileMimeType);
|
||||||
this.newFileContent = jotai.atom(null) as jotai.PrimitiveAtom<string | null>;
|
|
||||||
this.goParentDirectory = this.goParentDirectory.bind(this);
|
this.goParentDirectory = this.goParentDirectory.bind(this);
|
||||||
|
|
||||||
const fullFileAtom = jotai.atom<Promise<FullFile>>(async (get) => {
|
const fullFileAtom = jotai.atom<Promise<FullFile>>(async (get) => {
|
||||||
@ -389,6 +391,23 @@ export class PreviewModel implements ViewModel {
|
|||||||
this.fullFile = fullFileAtom;
|
this.fullFile = fullFileAtom;
|
||||||
this.fileContent = fileContentAtom;
|
this.fileContent = fileContentAtom;
|
||||||
|
|
||||||
|
this.fileContentModified = jotai.atom();
|
||||||
|
this.fileContentLastSaved = jotai.atom();
|
||||||
|
this.previewFileContent = jotai.atom(
|
||||||
|
async (get) => {
|
||||||
|
const fileContentModified = get(this.fileContentModified);
|
||||||
|
const fileContentLastSaved = get(this.fileContentLastSaved);
|
||||||
|
if (!fileContentModified) {
|
||||||
|
return fileContentLastSaved ?? (await get(this.fileContent));
|
||||||
|
} else {
|
||||||
|
return fileContentModified;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(_get, set, value) => {
|
||||||
|
set(this.fileContentModified, value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.specializedView = jotai.atom<Promise<{ specializedView?: string; errorStr?: string }>>(async (get) => {
|
this.specializedView = jotai.atom<Promise<{ specializedView?: string; errorStr?: string }>>(async (get) => {
|
||||||
return this.getSpecializedView(get);
|
return this.getSpecializedView(get);
|
||||||
});
|
});
|
||||||
@ -533,7 +552,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newFileContent = globalStore.get(this.newFileContent);
|
const newFileContent = globalStore.get(this.fileContentModified);
|
||||||
if (newFileContent == null) {
|
if (newFileContent == null) {
|
||||||
console.log("not saving file, newFileContent is null");
|
console.log("not saving file, newFileContent is null");
|
||||||
return;
|
return;
|
||||||
@ -541,7 +560,8 @@ export class PreviewModel implements ViewModel {
|
|||||||
const conn = globalStore.get(this.connection) ?? "";
|
const conn = globalStore.get(this.connection) ?? "";
|
||||||
try {
|
try {
|
||||||
services.FileService.SaveFile(conn, filePath, util.stringToBase64(newFileContent));
|
services.FileService.SaveFile(conn, filePath, util.stringToBase64(newFileContent));
|
||||||
globalStore.set(this.newFileContent, null);
|
globalStore.set(this.fileContentModified, null);
|
||||||
|
globalStore.set(this.fileContentLastSaved, newFileContent);
|
||||||
console.log("saved file", filePath);
|
console.log("saved file", filePath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving file:", error);
|
console.error("Error saving file:", error);
|
||||||
@ -549,9 +569,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleFileRevert() {
|
async handleFileRevert() {
|
||||||
const fileContent = await globalStore.get(this.fileContent);
|
globalStore.set(this.fileContentModified, null);
|
||||||
this.monacoRef.current?.setValue(fileContent);
|
|
||||||
globalStore.set(this.newFileContent, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleOpenFile(filePath: string) {
|
async handleOpenFile(filePath: string) {
|
||||||
@ -621,7 +639,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
const loadableSV = globalStore.get(this.loadableSpecializedView);
|
const loadableSV = globalStore.get(this.loadableSpecializedView);
|
||||||
if (loadableSV.state == "hasData") {
|
if (loadableSV.state == "hasData") {
|
||||||
if (loadableSV.data.specializedView == "codeedit") {
|
if (loadableSV.data.specializedView == "codeedit") {
|
||||||
if (globalStore.get(this.newFileContent) != null) {
|
if (globalStore.get(this.fileContentModified) != null) {
|
||||||
menuItems.push({ type: "separator" });
|
menuItems.push({ type: "separator" });
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
label: "Save File",
|
label: "Save File",
|
||||||
@ -711,7 +729,11 @@ function MarkdownPreview({ model }: SpecializedViewProps) {
|
|||||||
}, [connName, fileInfo.dir]);
|
}, [connName, fileInfo.dir]);
|
||||||
return (
|
return (
|
||||||
<div className="view-preview view-preview-markdown">
|
<div className="view-preview view-preview-markdown">
|
||||||
<Markdown textAtom={model.fileContent} showTocAtom={model.markdownShowToc} resolveOpts={resolveOpts} />
|
<Markdown
|
||||||
|
textAtom={model.previewFileContent}
|
||||||
|
showTocAtom={model.markdownShowToc}
|
||||||
|
resolveOpts={resolveOpts}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -762,8 +784,7 @@ function StreamingPreview({ model }: SpecializedViewProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CodeEditPreview({ model }: SpecializedViewProps) {
|
function CodeEditPreview({ model }: SpecializedViewProps) {
|
||||||
const fileContent = jotai.useAtomValue(model.fileContent);
|
const setNewFileContent = jotai.useSetAtom(model.previewFileContent);
|
||||||
const setNewFileContent = jotai.useSetAtom(model.newFileContent);
|
|
||||||
const fileName = jotai.useAtomValue(model.statFilePath);
|
const fileName = jotai.useAtomValue(model.statFilePath);
|
||||||
|
|
||||||
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
|
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
|
||||||
@ -812,16 +833,16 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
text={fileContent}
|
textAtom={model.previewFileContent}
|
||||||
filename={fileName}
|
filename={fileName}
|
||||||
onChange={(text) => setNewFileContent(text)}
|
onChange={setNewFileContent}
|
||||||
onMount={onMount}
|
onMount={onMount}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CSVViewPreview({ model, parentRef }: SpecializedViewProps) {
|
function CSVViewPreview({ model, parentRef }: SpecializedViewProps) {
|
||||||
const fileContent = jotai.useAtomValue(model.fileContent);
|
const fileContent = jotai.useAtomValue(model.previewFileContent);
|
||||||
const fileName = jotai.useAtomValue(model.statFilePath);
|
const fileName = jotai.useAtomValue(model.statFilePath);
|
||||||
return <CSVView parentRef={parentRef} readonly={true} content={fileContent} filename={fileName} />;
|
return <CSVView parentRef={parentRef} readonly={true} content={fileContent} filename={fileName} />;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user