nandi/oripublic Fork 0
b6cd929ab1425a4ef6073ea9c189917cca02cf3c
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

PreviewPane.tsx · 159 lines · 3.8 KBTypeScript Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1/**
2 * PreviewPane shows a file from the workspace: rendered Markdown or AsciiDoc
3 * (with a Rendered/Source toggle), images at natural size, draw.io diagrams
4 * through the diagrams.net viewer (with the XML as Source), and
5 * syntax-highlighted read-only code for everything else.
6 *
7 * @example
8 * <PreviewPane path="README.md" />
9 */
10
11import { useEffect, useState } from "react";
12import { readFile } from "../api";
13import { type DocumentKind, kindForPath, languageForPath } from "../lang";
14import { openEditor } from "../workspace";
15import { AsciiDocView } from "./AsciiDocView";
16import { CodeView } from "./CodeView";
17import { DrawioView } from "./DrawioView";
18import { ImageView } from "./ImageView";
19import { Markdown } from "./Markdown";
20
21type PreviewMode = "rendered" | "source";
22
23/** Kinds with both a rendered form and a source form. */
24const toggleableKinds: ReadonlySet<DocumentKind> = new Set([
25 "markdown",
26 "asciidoc",
27 "drawio",
28]);
29
30/**
31 * usePreviewFile loads the file behind path (images excepted: the browser
32 * fetches their bytes itself) and resets the view mode whenever the path
33 * changes. `content` stays null while loading.
34 */
35function usePreviewFile(path: string | null) {
36 const [content, setContent] = useState<string | null>(null);
37 const [error, setError] = useState<string | null>(null);
38 const [mode, setMode] = useState<PreviewMode>("rendered");
39
40 useEffect(() => {
41 if (!path) {
42 return;
43 }
44 setContent(null);
45 setError(null);
46 setMode("rendered");
47 if (kindForPath(path) === "image") {
48 return;
49 }
50 readFile(path)
51 .then((file) => setContent(file.content))
52 .catch((cause: Error) => setError(cause.message));
53 }, [path]);
54
55 return { content, error, mode, setMode };
56}
57
58export function PreviewPane({ path }: { path: string | null }) {
59 const { content, error, mode, setMode } = usePreviewFile(path);
60 const kind = path ? kindForPath(path) : "code";
61
62 if (!path) {
63 return (
64 <p className="pane-empty">Select a file in the tree to preview it.</p>
65 );
66 }
67 if (error) {
68 return <p className="pane-error">{error}</p>;
69 }
70 if (kind !== "image" && content === null) {
71 return <p className="pane-empty">loading</p>;
72 }
73
74 const toggleable = toggleableKinds.has(kind);
75
76 return (
77 <div className="preview">
78 <div className="pane-header">
79 <span className="pane-path" title={path}>
80 {path.split("/").pop()}
81 </span>
82 {toggleable && (
83 <div className="pane-modes">
84 <ModeButton current={mode} mode="rendered" onSelect={setMode} />
85 <ModeButton current={mode} mode="source" onSelect={setMode} />
86 </div>
87 )}
88 {kind !== "image" && (
89 <button
90 type="button"
91 className="pane-action"
92 onClick={() => openEditor(path)}
93 >
94 Edit
95 </button>
96 )}
97 </div>
98 <div className="preview-body">
99 <PreviewContent
100 path={path}
101 kind={kind}
102 content={content ?? ""}
103 mode={toggleable ? mode : "rendered"}
104 />
105 </div>
106 </div>
107 );
108}
109
110function ModeButton({
111 current,
112 mode,
113 onSelect,
114}: {
115 current: PreviewMode;
116 mode: PreviewMode;
117 onSelect: (mode: PreviewMode) => void;
118}) {
119 return (
120 <button
121 type="button"
122 className={`pane-mode${current === mode ? " pane-mode-active" : ""}`}
123 onClick={() => onSelect(mode)}
124 >
125 {mode === "rendered" ? "Rendered" : "Source"}
126 </button>
127 );
128}
129
130function PreviewContent({
131 path,
132 kind,
133 content,
134 mode,
135}: {
136 path: string;
137 kind: DocumentKind;
138 content: string;
139 mode: PreviewMode;
140}) {
141 if (kind === "image") {
142 return <ImageView path={path} />;
143 }
144 if (mode === "source" || kind === "code") {
145 return <CodeView content={content} language={languageForPath(path)} />;
146 }
147 if (kind === "drawio") {
148 return <DrawioView xml={content} />;
149 }
150 return (
151 <div className="preview-rendered">
152 {kind === "markdown" ? (
153 <Markdown text={content} />
154 ) : (
155 <AsciiDocView content={content} />
156 )}
157 </div>
158 );
159}