|
|
@@ -23,84 +23,89 @@ import { Path, ApiPath, REPO_URL } from "@/app/constant";
|
|
|
import { Loading } from "./home";
|
|
|
import styles from "./artifacts.module.scss";
|
|
|
|
|
|
-export const HTMLPreview = forwardRef<
|
|
|
- {
|
|
|
- reload: () => void;
|
|
|
- },
|
|
|
- {
|
|
|
- code: string;
|
|
|
- autoHeight?: boolean;
|
|
|
- height?: number | string;
|
|
|
- onLoad?: (title?: string) => void;
|
|
|
- }
|
|
|
->(function HTMLPreview(props, ref) {
|
|
|
- const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
|
- const [frameId, setFrameId] = useState<string>(nanoid());
|
|
|
- const [iframeHeight, setIframeHeight] = useState(600);
|
|
|
- const [title, setTitle] = useState("");
|
|
|
- /*
|
|
|
- * https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
|
|
|
- * 1. using srcdoc
|
|
|
- * 2. using src with dataurl:
|
|
|
- * easy to share
|
|
|
- * length limit (Data URIs cannot be larger than 32,768 characters.)
|
|
|
- */
|
|
|
+type HTMLPreviewProps = {
|
|
|
+ code: string;
|
|
|
+ autoHeight?: boolean;
|
|
|
+ height?: number | string;
|
|
|
+ onLoad?: (title?: string) => void;
|
|
|
+};
|
|
|
|
|
|
- useEffect(() => {
|
|
|
- const handleMessage = (e: any) => {
|
|
|
- const { id, height, title } = e.data;
|
|
|
- setTitle(title);
|
|
|
- if (id == frameId) {
|
|
|
- setIframeHeight(height);
|
|
|
- }
|
|
|
- };
|
|
|
- window.addEventListener("message", handleMessage);
|
|
|
- return () => {
|
|
|
- window.removeEventListener("message", handleMessage);
|
|
|
- };
|
|
|
- }, [frameId]);
|
|
|
+export type HTMLPreviewHander = {
|
|
|
+ reload: () => void;
|
|
|
+};
|
|
|
|
|
|
- useImperativeHandle(ref, () => ({
|
|
|
- reload: () => {
|
|
|
- setFrameId(nanoid());
|
|
|
- },
|
|
|
- }));
|
|
|
+export const HTMLPreview = forwardRef<HTMLPreviewHander, HTMLPreviewProps>(
|
|
|
+ function HTMLPreview(props, ref) {
|
|
|
+ const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
|
+ const [frameId, setFrameId] = useState<string>(nanoid());
|
|
|
+ const [iframeHeight, setIframeHeight] = useState(600);
|
|
|
+ const [title, setTitle] = useState("");
|
|
|
+ /*
|
|
|
+ * https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
|
|
|
+ * 1. using srcdoc
|
|
|
+ * 2. using src with dataurl:
|
|
|
+ * easy to share
|
|
|
+ * length limit (Data URIs cannot be larger than 32,768 characters.)
|
|
|
+ */
|
|
|
|
|
|
- const height = useMemo(() => {
|
|
|
- if (!props.autoHeight) return props.height || 600;
|
|
|
- if (typeof props.height === "string") {
|
|
|
- return props.height;
|
|
|
- }
|
|
|
- const parentHeight = props.height || 600;
|
|
|
- return iframeHeight + 40 > parentHeight ? parentHeight : iframeHeight + 40;
|
|
|
- }, [props.autoHeight, props.height, iframeHeight]);
|
|
|
+ useEffect(() => {
|
|
|
+ const handleMessage = (e: any) => {
|
|
|
+ const { id, height, title } = e.data;
|
|
|
+ setTitle(title);
|
|
|
+ if (id == frameId) {
|
|
|
+ setIframeHeight(height);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ window.addEventListener("message", handleMessage);
|
|
|
+ return () => {
|
|
|
+ window.removeEventListener("message", handleMessage);
|
|
|
+ };
|
|
|
+ }, [frameId]);
|
|
|
|
|
|
- const srcDoc = useMemo(() => {
|
|
|
- const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
|
|
|
- if (props.code.includes("</head>")) {
|
|
|
- props.code.replace("</head>", "</head>" + script);
|
|
|
- }
|
|
|
- return props.code + script;
|
|
|
- }, [props.code, frameId]);
|
|
|
+ useImperativeHandle(ref, () => ({
|
|
|
+ reload: () => {
|
|
|
+ setFrameId(nanoid());
|
|
|
+ },
|
|
|
+ }));
|
|
|
|
|
|
- const handleOnLoad = () => {
|
|
|
- if (props?.onLoad) {
|
|
|
- props.onLoad(title);
|
|
|
- }
|
|
|
- };
|
|
|
+ const height = useMemo(() => {
|
|
|
+ if (!props.autoHeight) return props.height || 600;
|
|
|
+ if (typeof props.height === "string") {
|
|
|
+ return props.height;
|
|
|
+ }
|
|
|
+ const parentHeight = props.height || 600;
|
|
|
+ return iframeHeight + 40 > parentHeight
|
|
|
+ ? parentHeight
|
|
|
+ : iframeHeight + 40;
|
|
|
+ }, [props.autoHeight, props.height, iframeHeight]);
|
|
|
|
|
|
- return (
|
|
|
- <iframe
|
|
|
- className={styles["artifacts-iframe"]}
|
|
|
- key={frameId}
|
|
|
- ref={iframeRef}
|
|
|
- sandbox="allow-forms allow-modals allow-scripts"
|
|
|
- style={{ height }}
|
|
|
- srcDoc={srcDoc}
|
|
|
- onLoad={handleOnLoad}
|
|
|
- />
|
|
|
- );
|
|
|
-});
|
|
|
+ const srcDoc = useMemo(() => {
|
|
|
+ const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
|
|
|
+ if (props.code.includes("</head>")) {
|
|
|
+ props.code.replace("</head>", "</head>" + script);
|
|
|
+ }
|
|
|
+ return props.code + script;
|
|
|
+ }, [props.code, frameId]);
|
|
|
+
|
|
|
+ const handleOnLoad = () => {
|
|
|
+ if (props?.onLoad) {
|
|
|
+ props.onLoad(title);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <iframe
|
|
|
+ className={styles["artifacts-iframe"]}
|
|
|
+ key={frameId}
|
|
|
+ ref={iframeRef}
|
|
|
+ sandbox="allow-forms allow-modals allow-scripts"
|
|
|
+ style={{ height }}
|
|
|
+ srcDoc={srcDoc}
|
|
|
+ onLoad={handleOnLoad}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ },
|
|
|
+);
|
|
|
|
|
|
export function ArtifactsShareButton({
|
|
|
getCode,
|
|
|
@@ -203,7 +208,7 @@ export function Artifacts() {
|
|
|
const [code, setCode] = useState("");
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
const [fileName, setFileName] = useState("");
|
|
|
- const previewRef = useRef<typeof HTMLPreview>(null);
|
|
|
+ const previewRef = useRef<HTMLPreviewHander>(null);
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (id) {
|