From e3600f5acb45cb0bf37bacc05aa2c85ff03521ec Mon Sep 17 00:00:00 2001 From: Hk-Gosuto Date: Sun, 20 Oct 2024 17:04:24 +0800 Subject: [PATCH] feat: merge upstream artifacts features --- app/components/artifacts.module.scss | 31 ++++ app/components/artifacts.tsx | 266 +++++++++++++++++++++++++++ app/components/button.tsx | 14 +- app/components/markdown.tsx | 188 ++++++++++++++++--- app/components/mask.tsx | 35 ++++ app/components/settings.tsx | 33 ++++ app/components/ui-lib.tsx | 135 +++++++++++--- app/constant.ts | 2 + app/locales/cn.ts | 12 ++ app/locales/en.ts | 13 ++ app/store/config.ts | 4 + app/store/mask.ts | 2 + 12 files changed, 687 insertions(+), 48 deletions(-) create mode 100644 app/components/artifacts.module.scss create mode 100644 app/components/artifacts.tsx diff --git a/app/components/artifacts.module.scss b/app/components/artifacts.module.scss new file mode 100644 index 000000000..6bd0fd9cf --- /dev/null +++ b/app/components/artifacts.module.scss @@ -0,0 +1,31 @@ +.artifacts { + display: flex; + width: 100%; + height: 100%; + flex-direction: column; + &-header { + display: flex; + align-items: center; + height: 36px; + padding: 20px; + background: var(--second); + } + &-title { + flex: 1; + text-align: center; + font-weight: bold; + font-size: 24px; + } + &-content { + flex-grow: 1; + padding: 0 20px 20px 20px; + background-color: var(--second); + } +} + +.artifacts-iframe { + width: 100%; + border: var(--border-in-light); + border-radius: 6px; + background-color: var(--gray); +} diff --git a/app/components/artifacts.tsx b/app/components/artifacts.tsx new file mode 100644 index 000000000..ce187fbcb --- /dev/null +++ b/app/components/artifacts.tsx @@ -0,0 +1,266 @@ +import { + useEffect, + useState, + useRef, + useMemo, + forwardRef, + useImperativeHandle, +} from "react"; +import { useParams } from "react-router"; +import { IconButton } from "./button"; +import { nanoid } from "nanoid"; +import ExportIcon from "../icons/share.svg"; +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"; +import { Path, ApiPath, REPO_URL } from "@/app/constant"; +import { Loading } from "./home"; +import styles from "./artifacts.module.scss"; + +type HTMLPreviewProps = { + code: string; + autoHeight?: boolean; + height?: number | string; + onLoad?: (title?: string) => void; +}; + +export type HTMLPreviewHander = { + reload: () => void; +}; + +export const HTMLPreview = forwardRef( + function HTMLPreview(props, ref) { + const iframeRef = useRef(null); + const [frameId, setFrameId] = useState(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.) + */ + + 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]); + + useImperativeHandle(ref, () => ({ + reload: () => { + setFrameId(nanoid()); + }, + })); + + 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]); + + const srcDoc = useMemo(() => { + const script = ``; + if (props.code.includes("")) { + props.code.replace("", "" + script); + } + return script + props.code; + }, [props.code, frameId]); + + const handleOnLoad = () => { + if (props?.onLoad) { + props.onLoad(title); + } + }; + + return ( +