Skip to content

Commit 77bbf74

Browse files
authored
fix bug with CodeEditor/monaco model, more preview refactoring (#2353)
the primary purpose of this PR is to fix a showstopper bug in the CodeEditor component by setting "path" to a stable UUID. the bug was that it started as empty string, so it created a shared model between all of the codeeditor components. now each will get their own monaco model. also took this opportunity to do more more preview view refactoring, splitting up code, and more tailwind migrations.
1 parent 50cc08a commit 77bbf74

13 files changed

Lines changed: 468 additions & 597 deletions

frontend/app/element/markdown-util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export const resolveRemoteFile = async (filepath: string, resolveOpts: MarkdownR
162162
const baseDirUri = formatRemoteUri(resolveOpts.baseDir, resolveOpts.connName);
163163
const fileInfo = await RpcApi.FileJoinCommand(TabRpcClient, [baseDirUri, filepath]);
164164
const remoteUri = formatRemoteUri(fileInfo.path, resolveOpts.connName);
165-
console.log("markdown resolve", resolveOpts, filepath, "=>", baseDirUri, remoteUri);
165+
// console.log("markdown resolve", resolveOpts, filepath, "=>", baseDirUri, remoteUri);
166166
const usp = new URLSearchParams();
167167
usp.set("path", remoteUri);
168168
return getWebServerEndpoint() + "/wave/stream-file?" + usp.toString();

frontend/app/element/markdown.tsx

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
transformBlocks,
1111
} from "@/app/element/markdown-util";
1212
import remarkMermaidToTag from "@/app/element/remark-mermaid-to-tag";
13-
import { boundNumber, useAtomValueSafe } from "@/util/util";
13+
import { boundNumber, useAtomValueSafe, cn } from "@/util/util";
1414
import clsx from "clsx";
1515
import { Atom } from "jotai";
1616
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
@@ -297,6 +297,7 @@ type MarkdownProps = {
297297
showTocAtom?: Atom<boolean>;
298298
style?: React.CSSProperties;
299299
className?: string;
300+
contentClassName?: string;
300301
onClickExecute?: (cmd: string) => void;
301302
resolveOpts?: MarkdownResolveOpts;
302303
scrollable?: boolean;
@@ -311,6 +312,7 @@ const Markdown = ({
311312
showTocAtom,
312313
style,
313314
className,
315+
contentClassName,
314316
resolveOpts,
315317
fontSizeOverride,
316318
fixedFontSizeOverride,
@@ -383,19 +385,30 @@ const Markdown = ({
383385
};
384386

385387
const toc = useMemo(() => {
386-
if (showToc && tocRef.current.length > 0) {
387-
return tocRef.current.map((item) => {
388+
if (showToc) {
389+
if (tocRef.current.length > 0) {
390+
return tocRef.current.map((item) => {
391+
return (
392+
<a
393+
key={item.href}
394+
className="toc-item"
395+
style={{ "--indent-factor": item.depth } as React.CSSProperties}
396+
onClick={() => setFocusedHeading(item.href)}
397+
>
398+
{item.value}
399+
</a>
400+
);
401+
});
402+
} else {
388403
return (
389-
<a
390-
key={item.href}
391-
className="toc-item"
392-
style={{ "--indent-factor": item.depth } as React.CSSProperties}
393-
onClick={() => setFocusedHeading(item.href)}
404+
<div
405+
className="toc-item toc-empty text-secondary"
406+
style={{ "--indent-factor": 2 } as React.CSSProperties}
394407
>
395-
{item.value}
396-
</a>
408+
No sub-headings found
409+
</div>
397410
);
398-
});
411+
}
399412
}
400413
}, [showToc, tocRef]);
401414

@@ -444,7 +457,7 @@ const Markdown = ({
444457
return (
445458
<OverlayScrollbarsComponent
446459
ref={contentsOsRef}
447-
className="content"
460+
className={cn("content", contentClassName)}
448461
options={{ scrollbars: { autoHide: "leave" } }}
449462
>
450463
<ReactMarkdown
@@ -460,7 +473,7 @@ const Markdown = ({
460473

461474
const NonScrollableMarkdown = () => {
462475
return (
463-
<div className="content non-scrollable">
476+
<div className={cn("content non-scrollable", contentClassName)}>
464477
<ReactMarkdown
465478
remarkPlugins={remarkPlugins}
466479
rehypePlugins={rehypePlugins}
@@ -483,9 +496,9 @@ const Markdown = ({
483496
<div className={clsx("markdown", className)} style={mergedStyle}>
484497
{scrollable ? <ScrollableMarkdown /> : <NonScrollableMarkdown />}
485498
{toc && (
486-
<OverlayScrollbarsComponent className="toc" options={{ scrollbars: { autoHide: "leave" } }}>
499+
<OverlayScrollbarsComponent className="toc mt-1" options={{ scrollbars: { autoHide: "leave" } }}>
487500
<div className="toc-inner">
488-
<h4>Table of Contents</h4>
501+
<h4 className="font-bold">Table of Contents</h4>
489502
{toc}
490503
</div>
491504
</OverlayScrollbarsComponent>

frontend/app/view/codeeditor/codeeditor.tsx

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
88
import { configureMonacoYaml } from "monaco-yaml";
99
import React, { useMemo, useRef } from "react";
1010

11-
import { RpcApi } from "@/app/store/wshclientapi";
12-
import { TabRpcClient } from "@/app/store/wshrpcutil";
13-
import { boundNumber, makeConnRoute } from "@/util/util";
11+
import { boundNumber } from "@/util/util";
1412
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
1513
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
1614
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
@@ -19,7 +17,6 @@ import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"
1917
import { SchemaEndpoints, getSchemaEndpointInfo } from "./schemaendpoints";
2018
import ymlWorker from "./yamlworker?worker";
2119

22-
2320
// there is a global monaco variable (TODO get the correct TS type)
2421
declare var monaco: Monaco;
2522

@@ -109,23 +106,21 @@ function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions {
109106
interface CodeEditorProps {
110107
blockId: string;
111108
text: string;
112-
filename: string;
113-
fileinfo: FileInfo;
109+
readonly: boolean;
114110
language?: string;
115-
meta?: MetaType;
116111
onChange?: (text: string) => void;
117112
onMount?: (monacoPtr: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => () => void;
118113
}
119114

120-
export function CodeEditor({ blockId, text, language, filename, fileinfo, meta, onChange, onMount }: CodeEditorProps) {
115+
export function CodeEditor({ blockId, text, language, readonly, onChange, onMount }: CodeEditorProps) {
121116
const divRef = useRef<HTMLDivElement>(null);
122117
const unmountRef = useRef<() => void>(null);
123118
const minimapEnabled = useOverrideConfigAtom(blockId, "editor:minimapenabled") ?? false;
124119
const stickyScrollEnabled = useOverrideConfigAtom(blockId, "editor:stickyscrollenabled") ?? false;
125120
const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false;
126121
const fontSize = boundNumber(useOverrideConfigAtom(blockId, "editor:fontsize"), 6, 64);
127122
const theme = "wave-theme-dark";
128-
const [absPath, setAbsPath] = React.useState("");
123+
const editorPath = useRef(crypto.randomUUID()).current;
129124

130125
React.useEffect(() => {
131126
return () => {
@@ -136,24 +131,6 @@ export function CodeEditor({ blockId, text, language, filename, fileinfo, meta,
136131
};
137132
}, []);
138133

139-
React.useEffect(() => {
140-
const inner = async () => {
141-
try {
142-
const fileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [filename], {
143-
route: makeConnRoute(meta.connection ?? ""),
144-
});
145-
setAbsPath(fileInfo.path);
146-
} catch (e) {
147-
setAbsPath(filename);
148-
}
149-
};
150-
inner();
151-
}, [filename]);
152-
153-
React.useEffect(() => {
154-
console.log("abspath is", absPath);
155-
}, [absPath]);
156-
157134
function handleEditorChange(text: string, ev: MonacoTypes.editor.IModelContentChangedEvent) {
158135
if (onChange) {
159136
onChange(text);
@@ -168,13 +145,13 @@ export function CodeEditor({ blockId, text, language, filename, fileinfo, meta,
168145

169146
const editorOpts = useMemo(() => {
170147
const opts = defaultEditorOptions();
171-
opts.readOnly = fileinfo.readonly;
148+
opts.readOnly = readonly;
172149
opts.minimap.enabled = minimapEnabled;
173150
opts.stickyScroll.enabled = stickyScrollEnabled;
174151
opts.wordWrap = wordWrap ? "on" : "off";
175152
opts.fontSize = fontSize;
176153
return opts;
177-
}, [minimapEnabled, stickyScrollEnabled, wordWrap, fontSize, fileinfo.readonly]);
154+
}, [minimapEnabled, stickyScrollEnabled, wordWrap, fontSize, readonly]);
178155

179156
return (
180157
<div className="flex flex-col w-full h-full overflow-hidden items-center justify-center">
@@ -185,7 +162,7 @@ export function CodeEditor({ blockId, text, language, filename, fileinfo, meta,
185162
options={editorOpts}
186163
onChange={handleEditorChange}
187164
onMount={handleEditorOnMount}
188-
path={absPath}
165+
path={editorPath}
189166
language={language}
190167
/>
191168
</div>

frontend/app/view/preview/directorypreview.scss

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,6 @@
9090
display: flex;
9191
flex-direction: column;
9292
padding: 0 5px 5px 5px;
93-
.dir-table-body-search-display {
94-
display: flex;
95-
border-radius: 3px;
96-
padding: 0.25rem 0.5rem;
97-
background-color: var(--warning-color);
98-
99-
.search-display-close-button {
100-
margin-left: auto;
101-
}
102-
}
10393

10494
.dir-table-body-scroll-box {
10595
position: relative;
@@ -185,52 +175,6 @@
185175
}
186176
}
187177
}
188-
189-
.dir-table-search-line {
190-
display: flex;
191-
justify-content: flex-end;
192-
gap: 0.7rem;
193-
194-
.dir-table-search-box {
195-
width: 0;
196-
height: 0;
197-
opacity: 0;
198-
padding: 0;
199-
border: none;
200-
pointer-events: none;
201-
}
202-
}
203-
}
204-
205-
.dir-table-button {
206-
background-color: transparent;
207-
display: flex;
208-
justify-content: center;
209-
align-items: center;
210-
flex-direction: column;
211-
padding: 0.2rem;
212-
border-radius: 6px;
213-
214-
input {
215-
width: 0;
216-
height: 0;
217-
opacity: 0;
218-
padding: 0;
219-
border: none;
220-
pointer-events: none;
221-
}
222-
223-
&:hover {
224-
background-color: var(--highlight-bg-color);
225-
}
226-
227-
&:focus {
228-
background-color: var(--highlight-bg-color);
229-
}
230-
231-
&:focus-within {
232-
background-color: var(--highlight-bg-color);
233-
}
234178
}
235179

236180
.entry-manager-overlay {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { Button } from "@/app/element/button";
5+
import { Input } from "@/app/element/input";
6+
import React, { memo, useState } from "react";
7+
8+
export enum EntryManagerType {
9+
NewFile = "New File",
10+
NewDirectory = "New Folder",
11+
EditName = "Rename",
12+
}
13+
14+
export type EntryManagerOverlayProps = {
15+
forwardRef?: React.Ref<HTMLDivElement>;
16+
entryManagerType: EntryManagerType;
17+
startingValue?: string;
18+
onSave: (newValue: string) => void;
19+
onCancel?: () => void;
20+
style?: React.CSSProperties;
21+
getReferenceProps?: () => any;
22+
};
23+
24+
export const EntryManagerOverlay = memo(
25+
({
26+
entryManagerType,
27+
startingValue,
28+
onSave,
29+
onCancel,
30+
forwardRef,
31+
style,
32+
getReferenceProps,
33+
}: EntryManagerOverlayProps) => {
34+
const [value, setValue] = useState(startingValue);
35+
return (
36+
<div className="entry-manager-overlay" ref={forwardRef} style={style} {...(getReferenceProps?.() ?? {})}>
37+
<div className="entry-manager-type">{entryManagerType}</div>
38+
<div className="entry-manager-input">
39+
<Input
40+
value={value}
41+
onChange={setValue}
42+
autoFocus={true}
43+
onKeyDown={(e) => {
44+
if (e.key === "Enter") {
45+
e.preventDefault();
46+
e.stopPropagation();
47+
onSave(value);
48+
}
49+
}}
50+
/>
51+
</div>
52+
<div className="entry-manager-buttons">
53+
<Button className="vertical-padding-4" onClick={() => onSave(value)}>
54+
Save
55+
</Button>
56+
<Button className="vertical-padding-4 red outlined" onClick={onCancel}>
57+
Cancel
58+
</Button>
59+
</div>
60+
</div>
61+
);
62+
}
63+
);
64+
65+
EntryManagerOverlay.displayName = "EntryManagerOverlay";

0 commit comments

Comments
 (0)