浏览代码

frat: add reload button

lloydzhou 1 年之前
父节点
当前提交
b0e9a542ba
共有 2 个文件被更改,包括 55 次插入16 次删除
  1. 43 16
      app/components/artifacts.tsx
  2. 12 0
      app/components/markdown.tsx

+ 43 - 16
app/components/artifacts.tsx

@@ -1,4 +1,11 @@
-import { useEffect, useState, useRef, useMemo } from "react";
+import {
+  useEffect,
+  useState,
+  useRef,
+  useMemo,
+  forwardRef,
+  useImperativeHandle,
+} from "react";
 import { useParams } from "react-router";
 import { useWindowSize } from "@/app/utils";
 import { IconButton } from "./button";
@@ -8,6 +15,7 @@ import CopyIcon from "../icons/copy.svg";
 import DownloadIcon from "../icons/download.svg";
 import GithubIcon from "../icons/github.svg";
 import LoadingButtonIcon from "../icons/loading.svg";
+import ReloadButtonIcon from "../icons/reload.svg";
 import Locale from "../locales";
 import { Modal, showToast } from "./ui-lib";
 import { copyToClipboard, downloadAs } from "../utils";
@@ -15,14 +23,19 @@ import { Path, ApiPath, REPO_URL } from "@/app/constant";
 import { Loading } from "./home";
 import styles from "./artifacts.module.scss";
 
-export function HTMLPreview(props: {
-  code: string;
-  autoHeight?: boolean;
-  height?: number | string;
-  onLoad?: (title?: string) => void;
-}) {
-  const ref = useRef<HTMLIFrameElement>(null);
-  const frameId = useRef<string>(nanoid());
+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("");
   /*
@@ -37,7 +50,7 @@ export function HTMLPreview(props: {
     const handleMessage = (e: any) => {
       const { id, height, title } = e.data;
       setTitle(title);
-      if (id == frameId.current) {
+      if (id == frameId) {
         setIframeHeight(height);
       }
     };
@@ -45,7 +58,13 @@ export function HTMLPreview(props: {
     return () => {
       window.removeEventListener("message", handleMessage);
     };
-  }, []);
+  }, [frameId]);
+
+  useImperativeHandle(ref, () => ({
+    reload: () => {
+      setFrameId(nanoid());
+    },
+  }));
 
   const height = useMemo(() => {
     if (!props.autoHeight) return props.height || 600;
@@ -57,12 +76,12 @@ export function HTMLPreview(props: {
   }, [props.autoHeight, props.height, iframeHeight]);
 
   const srcDoc = useMemo(() => {
-    const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId.current}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
+    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]);
+  }, [props.code, frameId]);
 
   const handleOnLoad = () => {
     if (props?.onLoad) {
@@ -73,15 +92,15 @@ export function HTMLPreview(props: {
   return (
     <iframe
       className={styles["artifacts-iframe"]}
-      id={frameId.current}
-      ref={ref}
+      key={frameId}
+      ref={iframeRef}
       sandbox="allow-forms allow-modals allow-scripts"
       style={{ height }}
       srcDoc={srcDoc}
       onLoad={handleOnLoad}
     />
   );
-}
+});
 
 export function ArtifactsShareButton({
   getCode,
@@ -184,6 +203,7 @@ export function Artifacts() {
   const [code, setCode] = useState("");
   const [loading, setLoading] = useState(true);
   const [fileName, setFileName] = useState("");
+  const previewRef = useRef<typeof HTMLPreview>(null);
 
   useEffect(() => {
     if (id) {
@@ -208,6 +228,12 @@ export function Artifacts() {
         <a href={REPO_URL} target="_blank" rel="noopener noreferrer">
           <IconButton bordered icon={<GithubIcon />} shadow />
         </a>
+        <IconButton
+          bordered
+          icon={<ReloadButtonIcon />}
+          shadow
+          onClick={() => previewRef.current?.reload()}
+        />
         <div className={styles["artifacts-title"]}>NextChat Artifacts</div>
         <ArtifactsShareButton
           id={id}
@@ -220,6 +246,7 @@ export function Artifacts() {
         {code && (
           <HTMLPreview
             code={code}
+            ref={previewRef}
             autoHeight={false}
             height={"100%"}
             onLoad={(title) => {

+ 12 - 0
app/components/markdown.tsx

@@ -10,12 +10,15 @@ import { copyToClipboard, useWindowSize } from "../utils";
 import mermaid from "mermaid";
 
 import LoadingIcon from "../icons/three-dots.svg";
+import ReloadButtonIcon from "../icons/reload.svg";
 import React from "react";
 import { useDebouncedCallback } from "use-debounce";
 import { showImageModal, FullScreen } from "./ui-lib";
 import { ArtifactsShareButton, HTMLPreview } from "./artifacts";
 import { Plugin } from "../constant";
 import { useChatStore } from "../store";
+import { IconButton } from "./button";
+
 export function Mermaid(props: { code: string }) {
   const ref = useRef<HTMLDivElement>(null);
   const [hasError, setHasError] = useState(false);
@@ -64,6 +67,7 @@ export function Mermaid(props: { code: string }) {
 
 export function PreCode(props: { children: any }) {
   const ref = useRef<HTMLPreElement>(null);
+  const previewRef = useRef<typeof HTMLPreview>(null);
   const [mermaidCode, setMermaidCode] = useState("");
   const [htmlCode, setHtmlCode] = useState("");
   const { height } = useWindowSize();
@@ -141,7 +145,15 @@ export function PreCode(props: { children: any }) {
             style={{ position: "absolute", right: 20, top: 10 }}
             getCode={() => htmlCode}
           />
+          <IconButton
+            style={{ position: "absolute", right: 120, top: 10 }}
+            bordered
+            icon={<ReloadButtonIcon />}
+            shadow
+            onClick={() => previewRef.current?.reload()}
+          />
           <HTMLPreview
+            ref={previewRef}
             code={htmlCode}
             autoHeight={!document.fullscreenElement}
             height={!document.fullscreenElement ? 600 : height}