Skip to content

Commit 72feb72

Browse files
stevenvoclaude
andauthored
Add manual refresh buttons to file preview (#2680)
## Summary Adds refresh icon buttons to all file preview types, allowing users to reload file content from disk without closing and reopening the preview. ## Problem When files are modified externally (by another editor, git operations, build tools), the Wave Terminal file preview doesn't automatically update. Users had to close and reopen the preview to see changes. ## Solution - Added refresh button (rotating arrows icon) to preview header - Appears for: code files, markdown, PDFs, images, videos, audio - Clicking refresh reloads the file from disk - Uses existing refreshCallback pattern (already used by directory preview) ## Implementation - Modified preview-model.tsx to add refresh button to endIconButtons - Made fullFileAtom reactive to refreshVersion changes - Implemented refreshCallback in all preview components: - preview-edit.tsx (code editor) - preview-markdown.tsx (markdown files) - preview-streaming.tsx (PDFs, images, videos, audio) ## Test Plan - [x] Open code file in preview - [x] Modify file externally - [x] Click refresh button -> sees updated content - [x] Test with markdown, PDF, image files - [x] Verify button appears in header 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent fbb0c4d commit 72feb72

File tree

4 files changed

+41
-2
lines changed

4 files changed

+41
-2
lines changed

frontend/app/view/preview/preview-edit.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,13 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
6363

6464
useEffect(() => {
6565
model.codeEditKeyDownHandler = codeEditKeyDownHandler;
66+
model.refreshCallback = () => {
67+
globalStore.set(model.refreshVersion, (v) => v + 1);
68+
};
6669
return () => {
6770
model.codeEditKeyDownHandler = null;
6871
model.monacoRef.current = null;
72+
model.refreshCallback = null;
6973
};
7074
}, []);
7175

frontend/app/view/preview/preview-markdown.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { Markdown } from "@/element/markdown";
5-
import { getOverrideConfigAtom } from "@/store/global";
5+
import { getOverrideConfigAtom, globalStore } from "@/store/global";
66
import { useAtomValue } from "jotai";
7-
import { useMemo } from "react";
7+
import { useEffect, useMemo } from "react";
88
import type { SpecializedViewProps } from "./preview";
99

1010
function MarkdownPreview({ model }: SpecializedViewProps) {
11+
useEffect(() => {
12+
model.refreshCallback = () => {
13+
globalStore.set(model.refreshVersion, (v) => v + 1);
14+
};
15+
return () => {
16+
model.refreshCallback = null;
17+
};
18+
}, []);
1119
const connName = useAtomValue(model.connection);
1220
const fileInfo = useAtomValue(model.statFile);
1321
const fontSizeOverride = useAtomValue(getOverrideConfigAtom(model.blockId, "markdown:fontsize"));

frontend/app/view/preview/preview-model.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,22 @@ export class PreviewModel implements ViewModel {
350350
title: "Table of Contents",
351351
click: () => this.markdownShowTocToggle(),
352352
},
353+
{
354+
elemtype: "iconbutton",
355+
icon: "arrows-rotate",
356+
title: "Refresh",
357+
click: () => this.refreshCallback?.(),
358+
},
359+
] as IconButtonDecl[];
360+
} else if (!isCeView && mimeType) {
361+
// For all other file types (text, code, etc.), add refresh button
362+
return [
363+
{
364+
elemtype: "iconbutton",
365+
icon: "arrows-rotate",
366+
title: "Refresh",
367+
click: () => this.refreshCallback?.(),
368+
},
353369
] as IconButtonDecl[];
354370
}
355371
return null;
@@ -408,6 +424,7 @@ export class PreviewModel implements ViewModel {
408424
this.goParentDirectory = this.goParentDirectory.bind(this);
409425

410426
const fullFileAtom = atom<Promise<FileData>>(async (get) => {
427+
get(this.refreshVersion); // Subscribe to refreshVersion to trigger re-fetch
411428
const fileName = get(this.metaFilePath);
412429
const path = await this.formatRemoteUri(fileName, get);
413430
if (fileName == null) {

frontend/app/view/preview/preview-streaming.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
import { Button } from "@/app/element/button";
55
import { CenteredDiv } from "@/app/element/quickelems";
6+
import { globalStore } from "@/store/global";
67
import { getWebServerEndpoint } from "@/util/endpoints";
78
import { formatRemoteUri } from "@/util/waveutil";
89
import { useAtomValue } from "jotai";
10+
import { useEffect } from "react";
911
import { TransformComponent, TransformWrapper, useControls } from "react-zoom-pan-pinch";
1012
import type { SpecializedViewProps } from "./preview";
1113

@@ -45,6 +47,14 @@ function StreamingImagePreview({ url }: { url: string }) {
4547
}
4648

4749
function StreamingPreview({ model }: SpecializedViewProps) {
50+
useEffect(() => {
51+
model.refreshCallback = () => {
52+
globalStore.set(model.refreshVersion, (v) => v + 1);
53+
};
54+
return () => {
55+
model.refreshCallback = null;
56+
};
57+
}, []);
4858
const conn = useAtomValue(model.connection);
4959
const fileInfo = useAtomValue(model.statFile);
5060
const filePath = fileInfo.path;

0 commit comments

Comments
 (0)