save artifact content to cloudflare workers kv

This commit is contained in:
lloydzhou 2024-07-24 20:36:11 +08:00
parent 421bf33c0e
commit e31bec3aff
1 changed files with 46 additions and 31 deletions

View File

@ -17,10 +17,12 @@ export function HTMLPreview(props: {
code: string; code: string;
autoHeight?: boolean; autoHeight?: boolean;
height?: number; height?: number;
onLoad?: (title?: string) => void;
}) { }) {
const ref = useRef<HTMLIFrameElement>(null); const ref = useRef<HTMLIFrameElement>(null);
const frameId = useRef<string>(nanoid()); const frameId = useRef<string>(nanoid());
const [iframeHeight, setIframeHeight] = useState(600); 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 * https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
* 1. using srcdoc * 1. using srcdoc
@ -31,9 +33,9 @@ export function HTMLPreview(props: {
useEffect(() => { useEffect(() => {
window.addEventListener("message", (e) => { window.addEventListener("message", (e) => {
const { id, height } = e.data; const { id, height, title } = e.data;
setTitle(title);
if (id == frameId.current) { if (id == frameId.current) {
console.log("setHeight", height);
setIframeHeight(height); setIframeHeight(height);
} }
}); });
@ -65,32 +67,34 @@ export function HTMLPreview(props: {
style={{ width: "100%", height }} style={{ width: "100%", height }}
// src={`data:text/html,${encodeURIComponent(srcDoc)}`} // src={`data:text/html,${encodeURIComponent(srcDoc)}`}
srcDoc={srcDoc} srcDoc={srcDoc}
onLoad={(e) => props?.onLoad(title)}
></iframe> ></iframe>
); );
} }
export function ArtifactShareButton({ getCode, id, style }) { export function ArtifactShareButton({ getCode, id, style, fileName }) {
const [name, setName] = useState(id); const [name, setName] = useState(id);
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const shareUrl = useMemo(() => const shareUrl = useMemo(() =>
[location.origin, "#", Path.Artifact, "/", name].join(""), [location.origin, "#", Path.Artifact, "/", name].join(""),
); );
const upload = (code) => const upload = (code) =>
fetch(ApiPath.Artifact, { id
method: "POST", ? Promise.resolve({ id })
body: getCode(), : fetch(ApiPath.Artifact, {
}) method: "POST",
.then((res) => res.json()) body: getCode(),
.then(({ id }) => { })
if (id) { .then((res) => res.json())
setShow(true); .then(({ id }) => {
return setName(id); if (id) {
} return { id };
throw Error(); }
}) throw Error();
.catch((e) => { })
showToast(Locale.Export.Artifact.Error); .catch((e) => {
}); showToast(Locale.Export.Artifact.Error);
});
return ( return (
<> <>
<div className="window-action-button" style={style}> <div className="window-action-button" style={style}>
@ -99,7 +103,10 @@ export function ArtifactShareButton({ getCode, id, style }) {
bordered bordered
title={Locale.Export.Artifact.Title} title={Locale.Export.Artifact.Title}
onClick={() => { onClick={() => {
upload(getCode()); upload(getCode()).then(({ id }) => {
setShow(true);
setName(id);
});
}} }}
/> />
</div> </div>
@ -115,7 +122,7 @@ export function ArtifactShareButton({ getCode, id, style }) {
bordered bordered
text={Locale.Export.Download} text={Locale.Export.Download}
onClick={() => { onClick={() => {
downloadAs(getCode(), `${id}.html`).then(() => downloadAs(getCode(), `${fileName || name}.html`).then(() =>
setShow(false), setShow(false),
); );
}} }}
@ -146,6 +153,8 @@ export function ArtifactShareButton({ getCode, id, style }) {
export function Artifact() { export function Artifact() {
const { id } = useParams(); const { id } = useParams();
const [code, setCode] = useState(""); const [code, setCode] = useState("");
const [loading, setLoading] = useState(true);
const [fileName, setFileName] = useState("");
const { height } = useWindowSize(); const { height } = useWindowSize();
useEffect(() => { useEffect(() => {
@ -167,23 +176,29 @@ export function Artifact() {
> >
<div <div
style={{ style={{
height: 40, height: 36,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
padding: 12, padding: 12,
}} }}
> >
<div style={{ flex: 1 }}> <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> <div style={{ flex: 1, textAlign: "center" }}>NextChat Artifact</div>
</div> <ArtifactShareButton id={id} getCode={() => code} fileName={fileName} />
<ArtifactShareButton id={id} getCode={() => code} />
</div> </div>
{code ? ( {loading && <Loading />}
<HTMLPreview code={code} autoHeight={false} height={height - 40} /> {code && (
) : ( <HTMLPreview
<Loading /> code={code}
autoHeight={false}
height={height - 36}
onLoad={(title) => {
setFileName(title);
setLoading(false);
}}
/>
)} )}
</div> </div>
); );