From 13ac6af4d08be30acea5af6f26838d2962d4433b Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 10 Sep 2024 13:37:30 -0700 Subject: [PATCH] Keep changes in preview after save (#360) A bit hacky, but should allow preview to update to latest file contents after save --- frontend/app/element/markdown.tsx | 2 +- frontend/app/view/codeeditor/codeeditor.tsx | 14 ++++-- frontend/app/view/preview/preview.tsx | 51 +++++++++++++++------ 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/frontend/app/element/markdown.tsx b/frontend/app/element/markdown.tsx index 59925fdeb..44b0e280e 100644 --- a/frontend/app/element/markdown.tsx +++ b/frontend/app/element/markdown.tsx @@ -178,7 +178,7 @@ type MarkdownProps = { }; const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts, onClickExecute }: MarkdownProps) => { - const textAtomValue = useAtomValueSafe(textAtom); + const textAtomValue = useAtomValueSafe(textAtom); const tocRef = useRef([]); const showToc = useAtomValueSafe(showTocAtom) ?? false; const contentsOsRef = useRef(null); diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index dc544678d..ae9d56b98 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import { atoms } from "@/app/store/global"; +import { useAtomValueSafe } from "@/util/util"; import loader from "@monaco-editor/loader"; 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 React, { useMemo, useRef } from "react"; +import React, { useMemo, useRef, useState } from "react"; import "./codeeditor.less"; // there is a global monaco variable (TODO get the correct TS type) @@ -70,7 +71,8 @@ function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions { } interface CodeEditorProps { - text: string; + text?: string; + textAtom?: Atom | Atom>; filename: string; language?: string; onChange?: (text: string) => void; @@ -87,11 +89,13 @@ const stickyScrollEnabledAtom = atom((get) => { 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(null); const unmountRef = useRef<() => void>(null); const minimapEnabled = useAtomValue(minimapEnabledAtom); const stickyScrollEnabled = useAtomValue(stickyScrollEnabledAtom); + const textAtomValue = useAtomValueSafe(textAtom); + const [textValue] = useState(() => textAtomValue ?? text); const theme = "wave-theme-dark"; React.useEffect(() => { @@ -127,7 +131,7 @@ export function CodeEditor({ text, language, filename, onChange, onMount }: Code
>; fileMimeTypeLoadable: jotai.Atom>; fileContent: jotai.Atom>; - newFileContent: jotai.PrimitiveAtom; + fileContentLastSaved: jotai.PrimitiveAtom; + fileContentModified: jotai.PrimitiveAtom; + previewFileContent: jotai.WritableAtom, [value: string], void>; openFileModal: jotai.PrimitiveAtom; openFileError: jotai.PrimitiveAtom; @@ -208,6 +210,7 @@ export class PreviewModel implements ViewModel { const blockData = get(this.blockAtom); return blockData?.meta?.edit ?? false; }); + this.viewName = jotai.atom("Preview"); this.viewText = jotai.atom((get) => { let headerPath = get(this.metaFilePath); @@ -241,7 +244,7 @@ export class PreviewModel implements ViewModel { }, ]; let saveClassName = "secondary"; - if (get(this.newFileContent) !== null) { + if (get(this.fileContentModified) !== null) { saveClassName = "primary"; } if (isCeView) { @@ -368,7 +371,6 @@ export class PreviewModel implements ViewModel { return fileInfo?.mimetype; }); this.fileMimeTypeLoadable = loadable(this.fileMimeType); - this.newFileContent = jotai.atom(null) as jotai.PrimitiveAtom; this.goParentDirectory = this.goParentDirectory.bind(this); const fullFileAtom = jotai.atom>(async (get) => { @@ -389,6 +391,23 @@ export class PreviewModel implements ViewModel { this.fullFile = fullFileAtom; 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>(async (get) => { return this.getSpecializedView(get); }); @@ -533,7 +552,7 @@ export class PreviewModel implements ViewModel { if (filePath == null) { return; } - const newFileContent = globalStore.get(this.newFileContent); + const newFileContent = globalStore.get(this.fileContentModified); if (newFileContent == null) { console.log("not saving file, newFileContent is null"); return; @@ -541,7 +560,8 @@ export class PreviewModel implements ViewModel { const conn = globalStore.get(this.connection) ?? ""; try { 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); } catch (error) { console.error("Error saving file:", error); @@ -549,9 +569,7 @@ export class PreviewModel implements ViewModel { } async handleFileRevert() { - const fileContent = await globalStore.get(this.fileContent); - this.monacoRef.current?.setValue(fileContent); - globalStore.set(this.newFileContent, null); + globalStore.set(this.fileContentModified, null); } async handleOpenFile(filePath: string) { @@ -621,7 +639,7 @@ export class PreviewModel implements ViewModel { const loadableSV = globalStore.get(this.loadableSpecializedView); if (loadableSV.state == "hasData") { if (loadableSV.data.specializedView == "codeedit") { - if (globalStore.get(this.newFileContent) != null) { + if (globalStore.get(this.fileContentModified) != null) { menuItems.push({ type: "separator" }); menuItems.push({ label: "Save File", @@ -711,7 +729,11 @@ function MarkdownPreview({ model }: SpecializedViewProps) { }, [connName, fileInfo.dir]); return (
- +
); } @@ -762,8 +784,7 @@ function StreamingPreview({ model }: SpecializedViewProps) { } function CodeEditPreview({ model }: SpecializedViewProps) { - const fileContent = jotai.useAtomValue(model.fileContent); - const setNewFileContent = jotai.useSetAtom(model.newFileContent); + const setNewFileContent = jotai.useSetAtom(model.previewFileContent); const fileName = jotai.useAtomValue(model.statFilePath); function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean { @@ -812,16 +833,16 @@ function CodeEditPreview({ model }: SpecializedViewProps) { return ( setNewFileContent(text)} + onChange={setNewFileContent} onMount={onMount} /> ); } function CSVViewPreview({ model, parentRef }: SpecializedViewProps) { - const fileContent = jotai.useAtomValue(model.fileContent); + const fileContent = jotai.useAtomValue(model.previewFileContent); const fileName = jotai.useAtomValue(model.statFilePath); return ; }