frat: add reload button

This commit is contained in:
lloydzhou 2024-08-21 11:17:00 +08:00
parent 8b67536c23
commit b0e9a542ba
2 changed files with 55 additions and 16 deletions

View File

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

View File

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