Skip to content

Commit

Permalink
Merge pull request #459 from bento-platform/feat/html-display
Browse files Browse the repository at this point in the history
feat: HTML file display support
  • Loading branch information
davidlougheed authored Oct 29, 2024
2 parents 464c7ac + 29b5cb4 commit 1715182
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 4 deletions.
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ module.exports = {
"no-prototype-builtins": "off",
"react/display-name": "off",
"linebreak-style": ["error", "unix"],
quotes: ["error", "double"],
semi: ["error", "always"],
"semi-spacing": ["error"],
"no-var": ["error"],
Expand Down
10 changes: 7 additions & 3 deletions src/components/display/FileDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ import type { JSONType } from "@/types/json";

import AudioDisplay from "./AudioDisplay";
import CsvDisplay from "./CsvDisplay";
import DocxDisplay from "./DocxDisplay";
import HtmlDisplay from "./HtmlDisplay";
import ImageBlobDisplay from "./ImageBlobDisplay";
import JsonDisplay from "./JsonDisplay";
import VideoDisplay from "./VideoDisplay";
import XlsxDisplay from "./XlsxDisplay";
import MarkdownDisplay from "./MarkdownDisplay";
import DocxDisplay from "./DocxDisplay";
import PdfDisplay from "./PdfDisplay";
import VideoDisplay from "./VideoDisplay";
import XlsxDisplay from "./XlsxDisplay";
import type { BlobDisplayProps } from "./types";

SyntaxHighlighter.registerLanguage("bash", bash);
Expand Down Expand Up @@ -85,6 +86,7 @@ export const VIEWABLE_FILE_EXTENSIONS = [

// Documents
"docx",
"html",
"pdf",

// Tabular data
Expand Down Expand Up @@ -260,6 +262,8 @@ const FileDisplay = ({ uri, fileName, loading }: FileDisplayProps) => {
return <VideoDisplay contents={fc} loading={loadingFileContents} />;
} else if (fileExt === "json") {
return <WrappedJsonDisplay contents={fc} loading={loadingFileContents} />;
} else if (fileExt === "html") {
return <HtmlDisplay contents={fc} loading={loadingFileContents} />;
} else {
return <WrappedCodeDisplay contents={fc} fileExt={fileExt} loading={loadingFileContents} />;
}
Expand Down
80 changes: 80 additions & 0 deletions src/components/display/HtmlDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useEffect, useRef, useState } from "react";
import { Spin } from "antd";

import type { BlobDisplayProps } from "./types";
import { LoadingOutlined } from "@ant-design/icons";

const HtmlDisplay = ({ contents, loading }: BlobDisplayProps) => {
const iframeRef = useRef<HTMLIFrameElement>(null);

const [isConverting, setIsConverting] = useState(false);
const [iframeLoading, setIframeLoading] = useState(false);

// Effect used to define the iframe load listener used to turn off the iframe loading state after the iframe has
// finished loading the specified srcdoc (derived from contents.)
useEffect(() => {
const iframe = iframeRef.current;
if (!iframe) return;
const listener = () => {
setIframeLoading(false);
};
iframe.addEventListener("load", listener);
return () => iframe.removeEventListener("load", listener);
}, []);

useEffect(() => {
const iframe = iframeRef.current;

if (!iframe) return;
if (!contents) return;

setIsConverting(true);

contents
.text()
.then((html) => {
// If there isn't already a <base ...> tag, instruct the embedded HTML to open links in the parent (i.e., here)
// instead of inside the iframe.
let modifiedHtml = html;
if (!modifiedHtml.includes("<base target") && !modifiedHtml.includes("<base href")) {
// noinspection HtmlRequiredTitleElement
modifiedHtml = modifiedHtml.replace("<head>", '<head><base target="_parent" />');
}

// When we update the srcdoc attribute, it'll cause the iframe to re-load the content.
// When it finishes loading, it'll trigger a load event listener on the iframe, defined above in another
// useEffect, which turns iframeLoading back off. This process lets us render a loading indicator.
iframe?.setAttribute("srcdoc", modifiedHtml);
setIframeLoading(true);
})
.finally(() => setIsConverting(false));
}, [contents]);

// Three different loading states:
// - loading bytes from server
// - converting bytes to text (essentially instant)
// - iframe loading/rendering HTML - triggered by updating the srcdoc attribute
const isLoading = loading || isConverting || iframeLoading;

return (
<Spin spinning={isLoading} size="large" indicator={<LoadingOutlined spin={true} />}>
<iframe
ref={iframeRef}
sandbox="allow-downloads allow-scripts allow-top-navigation"
style={{
width: "90vw",
// Height of the iframe is 100% of the view height
// minus its border (2px)
// minus the padding of the parent modal (40px)
// minus the header of the modal (32px)
// minus a top and bottom outside margin set by the modal (50px top, matched bottom)
height: "calc(100vh - 2px - 40px - 32px - 100px)",
border: "1px solid #C6C6C6",
borderRadius: 3,
}}
></iframe>
</Spin>
);
};

export default HtmlDisplay;

0 comments on commit 1715182

Please sign in to comment.