2024-05-14 08:45:41 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2024-05-14 18:37:41 +02:00
|
|
|
import { Markdown } from "@/element/markdown";
|
2024-06-22 01:01:51 +02:00
|
|
|
import { getBackendHostPort, globalStore, useBlockAtom, useBlockCache } from "@/store/global";
|
2024-06-12 02:42:10 +02:00
|
|
|
import * as services from "@/store/services";
|
2024-05-28 21:12:28 +02:00
|
|
|
import * as WOS from "@/store/wos";
|
2024-05-14 21:29:41 +02:00
|
|
|
import * as util from "@/util/util";
|
2024-06-04 03:22:26 +02:00
|
|
|
import clsx from "clsx";
|
2024-05-28 21:12:28 +02:00
|
|
|
import * as jotai from "jotai";
|
2024-05-17 07:48:23 +02:00
|
|
|
import { CenteredDiv } from "../element/quickelems";
|
2024-06-14 01:49:25 +02:00
|
|
|
import { CodeEdit } from "./codeedit";
|
2024-05-29 09:00:36 +02:00
|
|
|
import { DirectoryPreview } from "./directorypreview";
|
2024-05-14 08:45:41 +02:00
|
|
|
|
|
|
|
import "./view.less";
|
|
|
|
|
2024-05-17 07:48:23 +02:00
|
|
|
const MaxFileSize = 1024 * 1024 * 10; // 10MB
|
|
|
|
|
2024-06-04 00:50:51 +02:00
|
|
|
function DirNav({ cwdAtom }: { cwdAtom: jotai.WritableAtom<string, [string], void> }) {
|
|
|
|
const [cwd, setCwd] = jotai.useAtom(cwdAtom);
|
2024-06-13 23:41:28 +02:00
|
|
|
if (cwd == null || cwd == "") {
|
|
|
|
return null;
|
|
|
|
}
|
2024-06-04 00:50:51 +02:00
|
|
|
let splitNav = [cwd];
|
|
|
|
let remaining = cwd;
|
|
|
|
|
|
|
|
let idx = remaining.lastIndexOf("/");
|
|
|
|
while (idx !== -1) {
|
|
|
|
remaining = remaining.substring(0, idx);
|
|
|
|
splitNav.unshift(remaining);
|
|
|
|
|
|
|
|
idx = remaining.lastIndexOf("/");
|
|
|
|
}
|
|
|
|
if (splitNav.length === 0) {
|
|
|
|
splitNav = [cwd];
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="view-nav">
|
2024-06-04 03:22:26 +02:00
|
|
|
{splitNav.map((item, idx) => {
|
2024-06-04 00:50:51 +02:00
|
|
|
let splitPath = item.split("/");
|
|
|
|
if (splitPath.length === 0) {
|
|
|
|
splitPath = [item];
|
|
|
|
}
|
2024-06-04 03:22:26 +02:00
|
|
|
const isLast = idx == splitNav.length - 1;
|
|
|
|
let baseName = splitPath[splitPath.length - 1];
|
|
|
|
if (!isLast) {
|
|
|
|
baseName += "/";
|
|
|
|
}
|
2024-06-04 00:50:51 +02:00
|
|
|
return (
|
2024-06-04 03:22:26 +02:00
|
|
|
<div
|
|
|
|
className={clsx("view-nav-item", isLast ? "current-file" : "clickable")}
|
|
|
|
key={`nav-item-${item}`}
|
|
|
|
onClick={isLast ? null : () => setCwd(item)}
|
|
|
|
>
|
2024-06-04 00:50:51 +02:00
|
|
|
{baseName}
|
2024-06-04 03:22:26 +02:00
|
|
|
</div>
|
2024-06-04 00:50:51 +02:00
|
|
|
);
|
|
|
|
})}
|
2024-06-04 03:22:26 +02:00
|
|
|
<div className="flex-spacer"></div>
|
2024-06-04 00:50:51 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-05-17 07:48:23 +02:00
|
|
|
function MarkdownPreview({ contentAtom }: { contentAtom: jotai.Atom<Promise<string>> }) {
|
2024-05-16 09:29:58 +02:00
|
|
|
const readmeText = jotai.useAtomValue(contentAtom);
|
2024-05-14 18:37:41 +02:00
|
|
|
return (
|
|
|
|
<div className="view-preview view-preview-markdown">
|
2024-05-14 21:29:41 +02:00
|
|
|
<Markdown text={readmeText} />
|
2024-05-14 18:37:41 +02:00
|
|
|
</div>
|
|
|
|
);
|
2024-05-17 07:48:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function StreamingPreview({ fileInfo }: { fileInfo: FileInfo }) {
|
|
|
|
const filePath = fileInfo.path;
|
2024-06-12 02:42:10 +02:00
|
|
|
const streamingUrl = getBackendHostPort() + "/wave/stream-file?path=" + encodeURIComponent(filePath);
|
2024-06-03 22:13:41 +02:00
|
|
|
if (fileInfo.mimetype == "application/pdf") {
|
|
|
|
return (
|
|
|
|
<div className="view-preview view-preview-pdf">
|
2024-06-14 20:10:54 +02:00
|
|
|
<iframe src={streamingUrl} width="95%" height="95%" name="pdfview" />
|
2024-06-03 22:13:41 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2024-05-17 07:48:23 +02:00
|
|
|
if (fileInfo.mimetype.startsWith("video/")) {
|
|
|
|
return (
|
|
|
|
<div className="view-preview view-preview-video">
|
|
|
|
<video controls>
|
|
|
|
<source src={streamingUrl} />
|
|
|
|
</video>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (fileInfo.mimetype.startsWith("audio/")) {
|
|
|
|
return (
|
|
|
|
<div className="view-preview view-preview-audio">
|
|
|
|
<audio controls>
|
|
|
|
<source src={streamingUrl} />
|
|
|
|
</audio>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (fileInfo.mimetype.startsWith("image/")) {
|
|
|
|
return (
|
|
|
|
<div className="view-preview view-preview-image">
|
|
|
|
<img src={streamingUrl} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return <CenteredDiv>Preview Not Supported</CenteredDiv>;
|
|
|
|
}
|
|
|
|
|
2024-06-22 01:22:59 +02:00
|
|
|
function CodeEditPreview({
|
|
|
|
contentAtom,
|
|
|
|
filename,
|
|
|
|
readonly,
|
|
|
|
}: {
|
|
|
|
contentAtom: jotai.Atom<Promise<string>>;
|
|
|
|
filename: string;
|
|
|
|
readonly: boolean;
|
|
|
|
}) {
|
|
|
|
const fileContent = jotai.useAtomValue(contentAtom);
|
|
|
|
return <CodeEdit readonly={true} text={fileContent} filename={filename} />;
|
|
|
|
}
|
|
|
|
|
2024-06-22 01:40:24 +02:00
|
|
|
function iconForFile(mimeType: string, fileName: string): string {
|
|
|
|
if (mimeType == "application/pdf") {
|
|
|
|
return "file-pdf";
|
|
|
|
} else if (mimeType.startsWith("image/")) {
|
|
|
|
return "image";
|
|
|
|
} else if (mimeType.startsWith("video/")) {
|
|
|
|
return "film";
|
|
|
|
} else if (mimeType.startsWith("audio/")) {
|
|
|
|
return "headphones";
|
|
|
|
} else if (mimeType.startsWith("text/markdown")) {
|
|
|
|
return "file-lines";
|
|
|
|
} else if (
|
|
|
|
mimeType.startsWith("text/") ||
|
|
|
|
(mimeType.startsWith("application/") &&
|
|
|
|
(mimeType.includes("json") || mimeType.includes("yaml") || mimeType.includes("toml")))
|
|
|
|
) {
|
|
|
|
return "file-code";
|
|
|
|
} else if (mimeType === "directory") {
|
|
|
|
if (fileName == "~" || fileName == "~/") {
|
|
|
|
return "home";
|
|
|
|
}
|
|
|
|
return "folder";
|
|
|
|
} else {
|
|
|
|
return "file";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 07:48:23 +02:00
|
|
|
function PreviewView({ blockId }: { blockId: string }) {
|
2024-05-29 09:00:36 +02:00
|
|
|
const blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
|
|
|
const fileNameAtom: jotai.WritableAtom<string, [string], void> = useBlockCache(blockId, "preview:filename", () =>
|
|
|
|
jotai.atom<string, [string], void>(
|
|
|
|
(get) => {
|
|
|
|
return get(blockAtom)?.meta?.file;
|
|
|
|
},
|
|
|
|
(get, set, update) => {
|
|
|
|
const blockId = get(blockAtom)?.oid;
|
2024-06-12 02:42:10 +02:00
|
|
|
services.ObjectService.UpdateObjectMeta(`block:${blockId}`, { file: update });
|
2024-05-29 09:00:36 +02:00
|
|
|
}
|
|
|
|
)
|
2024-05-16 09:29:58 +02:00
|
|
|
);
|
2024-05-17 07:48:23 +02:00
|
|
|
const statFileAtom = useBlockAtom(blockId, "preview:statfile", () =>
|
|
|
|
jotai.atom<Promise<FileInfo>>(async (get) => {
|
|
|
|
const fileName = get(fileNameAtom);
|
|
|
|
if (fileName == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-06-12 02:42:10 +02:00
|
|
|
// const statFile = await FileService.StatFile(fileName);
|
|
|
|
const statFile = await services.FileService.StatFile(fileName);
|
2024-05-17 07:48:23 +02:00
|
|
|
return statFile;
|
|
|
|
})
|
|
|
|
);
|
2024-05-16 09:29:58 +02:00
|
|
|
const fullFileAtom = useBlockAtom(blockId, "preview:fullfile", () =>
|
|
|
|
jotai.atom<Promise<FullFile>>(async (get) => {
|
|
|
|
const fileName = get(fileNameAtom);
|
|
|
|
if (fileName == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-06-12 02:42:10 +02:00
|
|
|
// const file = await FileService.ReadFile(fileName);
|
|
|
|
const file = await services.FileService.ReadFile(fileName);
|
2024-05-16 09:29:58 +02:00
|
|
|
return file;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
const fileMimeTypeAtom = useBlockAtom(blockId, "preview:mimetype", () =>
|
|
|
|
jotai.atom<Promise<string>>(async (get) => {
|
2024-05-17 07:48:23 +02:00
|
|
|
const fileInfo = await get(statFileAtom);
|
|
|
|
return fileInfo?.mimetype;
|
2024-05-16 09:29:58 +02:00
|
|
|
})
|
|
|
|
);
|
|
|
|
const fileContentAtom = useBlockAtom(blockId, "preview:filecontent", () =>
|
|
|
|
jotai.atom<Promise<string>>(async (get) => {
|
|
|
|
const fullFile = await get(fullFileAtom);
|
|
|
|
return util.base64ToString(fullFile?.data64);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
let mimeType = jotai.useAtomValue(fileMimeTypeAtom);
|
|
|
|
if (mimeType == null) {
|
|
|
|
mimeType = "";
|
|
|
|
}
|
2024-06-06 23:52:01 +02:00
|
|
|
let fileName = jotai.useAtomValue(fileNameAtom);
|
2024-05-17 07:48:23 +02:00
|
|
|
const fileInfo = jotai.useAtomValue(statFileAtom);
|
|
|
|
|
|
|
|
// handle streaming files here
|
2024-06-04 00:50:51 +02:00
|
|
|
let specializedView: React.ReactNode;
|
2024-06-22 01:40:24 +02:00
|
|
|
let blockIcon = iconForFile(mimeType, fileName);
|
2024-06-03 22:13:41 +02:00
|
|
|
if (
|
|
|
|
mimeType == "application/pdf" ||
|
|
|
|
mimeType.startsWith("video/") ||
|
|
|
|
mimeType.startsWith("audio/") ||
|
|
|
|
mimeType.startsWith("image/")
|
|
|
|
) {
|
2024-06-04 00:50:51 +02:00
|
|
|
specializedView = <StreamingPreview fileInfo={fileInfo} />;
|
|
|
|
} else if (fileInfo == null) {
|
2024-06-13 23:41:28 +02:00
|
|
|
specializedView = (
|
|
|
|
<CenteredDiv>File Not Found{util.isBlank(fileName) ? null : JSON.stringify(fileName)}</CenteredDiv>
|
|
|
|
);
|
2024-06-04 00:50:51 +02:00
|
|
|
} else if (fileInfo.size > MaxFileSize) {
|
|
|
|
specializedView = <CenteredDiv>File Too Large to Preview</CenteredDiv>;
|
|
|
|
} else if (mimeType === "text/markdown") {
|
|
|
|
specializedView = <MarkdownPreview contentAtom={fileContentAtom} />;
|
2024-06-05 02:58:29 +02:00
|
|
|
} else if (
|
2024-06-06 23:52:01 +02:00
|
|
|
mimeType.startsWith("text/") ||
|
|
|
|
(mimeType.startsWith("application/") &&
|
|
|
|
(mimeType.includes("json") || mimeType.includes("yaml") || mimeType.includes("toml")))
|
2024-06-05 02:58:29 +02:00
|
|
|
) {
|
2024-06-22 01:22:59 +02:00
|
|
|
specializedView = <CodeEditPreview readonly={true} contentAtom={fileContentAtom} filename={fileName} />;
|
2024-06-04 00:50:51 +02:00
|
|
|
} else if (mimeType === "directory") {
|
|
|
|
specializedView = <DirectoryPreview contentAtom={fileContentAtom} fileNameAtom={fileNameAtom} />;
|
|
|
|
} else {
|
|
|
|
specializedView = (
|
|
|
|
<div className="view-preview">
|
|
|
|
<div>Preview ({mimeType})</div>
|
|
|
|
</div>
|
|
|
|
);
|
2024-05-14 18:37:41 +02:00
|
|
|
}
|
2024-06-22 01:01:51 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
const blockIconOverrideAtom = useBlockAtom<string>(blockId, "blockicon:override", () => {
|
|
|
|
return jotai.atom<string>(null);
|
2024-06-22 01:11:34 +02:00
|
|
|
}) as jotai.PrimitiveAtom<string>;
|
2024-06-22 01:01:51 +02:00
|
|
|
globalStore.set(blockIconOverrideAtom, blockIcon);
|
|
|
|
}, 10);
|
2024-06-04 00:50:51 +02:00
|
|
|
|
2024-05-14 08:45:41 +02:00
|
|
|
return (
|
2024-06-04 00:50:51 +02:00
|
|
|
<div className="full-preview">
|
|
|
|
<DirNav cwdAtom={fileNameAtom} />
|
|
|
|
{specializedView}
|
2024-05-14 08:45:41 +02:00
|
|
|
</div>
|
|
|
|
);
|
2024-05-17 07:48:23 +02:00
|
|
|
}
|
2024-05-14 08:45:41 +02:00
|
|
|
|
|
|
|
export { PreviewView };
|