memos/web/src/components/ShareMemoImageDialog.tsx

118 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useRef, useState } from "react";
import { userService } from "../services";
import toImage from "../labs/html2image";
import { ANIMATION_DURATION, IMAGE_URL_REG } from "../helpers/consts";
import * as utils from "../helpers/utils";
import { generateDialog } from "./Dialog";
import Only from "./common/OnlyWhen";
import toastHelper from "./Toast";
import { formatMemoContent } from "./Memo";
import "../less/share-memo-image-dialog.less";
interface Props extends DialogProps {
memo: Memo;
}
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
const { memo: propsMemo, destroy } = props;
const { user: userinfo } = userService.getState();
const memo = {
...propsMemo,
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
};
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
const [shortcutImgUrl, setShortcutImgUrl] = useState("");
const [imgAmount, setImgAmount] = useState(imageUrls.length);
const memoElRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (imgAmount > 0) {
return;
}
setTimeout(() => {
if (!memoElRef.current) {
return;
}
toImage(memoElRef.current, {
backgroundColor: "#eaeaea",
pixelRatio: window.devicePixelRatio * 2,
})
.then((url) => {
setShortcutImgUrl(url);
})
.catch(() => {
// do nth
});
}, ANIMATION_DURATION);
}, [imgAmount]);
const handleCloseBtnClick = () => {
destroy();
};
const handleImageOnLoad = (ev: React.SyntheticEvent<HTMLImageElement>) => {
if (ev.type === "error") {
toastHelper.error("😟 Image load failed");
(ev.target as HTMLImageElement).remove();
}
setImgAmount(imgAmount - 1);
};
const handleDownloadBtnClick = () => {
const a = document.createElement("a");
a.href = shortcutImgUrl;
a.download = `memos-${utils.getDateTimeString(Date.now())}.png`;
a.click();
};
return (
<>
<div className="dialog-header-container">
<p className="title-text">
<span className="icon-text">🌄</span>Share Memo
</p>
<button className="btn close-btn" onClick={handleCloseBtnClick}>
<i className="fa-solid fa-xmark fa-lg icon-img"></i>
</button>
</div>
<div className="dialog-content-container">
<div className={`tip-words-container ${shortcutImgUrl ? "finish" : "loading"}`}>
<p className="tip-text">{shortcutImgUrl ? "Click to save the image 👇" : "Generating the screenshot..."}</p>
</div>
<div className="memo-container" ref={memoElRef}>
<Only when={shortcutImgUrl !== ""}>
<img className="memo-shortcut-img" onClick={handleDownloadBtnClick} src={shortcutImgUrl} />
</Only>
<span className="time-text">{memo.createdAtStr}</span>
<div className="memo-content-text" dangerouslySetInnerHTML={{ __html: formatMemoContent(memo.content) }}></div>
<Only when={imageUrls.length > 0}>
<div className="images-container">
{imageUrls.map((imgUrl, idx) => (
<img decoding="async" key={idx} src={imgUrl} onLoad={handleImageOnLoad} onError={handleImageOnLoad} />
))}
</div>
</Only>
<div className="watermark-container">
<span className="normal-text">
<span className="icon-text"></span> by <span className="name-text">{userinfo?.name}</span>
</span>
</div>
</div>
</div>
</>
);
};
export default function showShareMemoImageDialog(memo: Memo): void {
generateDialog(
{
className: "share-memo-image-dialog",
},
ShareMemoImageDialog,
{ memo }
);
}