refactor: preview image modal

This commit is contained in:
Tristan Yang
2025-08-10 18:11:55 +08:00
parent 880ef4f2cc
commit db525ea222
7 changed files with 1603 additions and 1903 deletions
+34 -32
View File
@@ -1,11 +1,11 @@
{
"name": "vocechat-web",
"version": "0.9.11",
"version": "0.9.15",
"homepage": "https://voce.chat",
"dependencies": {
"@metamask/onboarding": "^1.0.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.0",
"@radix-ui/react-popover": "^1.1.13",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.1",
"@radix-ui/react-popover": "^1.1.14",
"@react-oauth/google": "^0.12.2",
"@reduxjs/toolkit": "^2.8.2",
"@svgr/webpack": "^8.1.0",
@@ -24,50 +24,50 @@
"@udecode/plate-paragraph": "^33.0.0",
"@udecode/plate-trailing-block": "^33.0.0",
"@uiball/loaders": "^1.3.1",
"agora-rtc-sdk-ng": "^4.23.3",
"agora-rtc-sdk-ng": "^4.24.0",
"broadcast-channel": "^7.1.0",
"browserslist": "^4.24.5",
"browserslist": "^4.25.1",
"clsx": "^2.1.1",
"copy-image-clipboard": "^2.1.2",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
"dayjs": "^1.11.13",
"firebase": "^11.7.3",
"framer-motion": "12.11.3",
"fs-extra": "^11.3.0",
"framer-motion": "12.23.12",
"fs-extra": "^11.3.1",
"heic2any": "^0.0.4",
"html-webpack-plugin": "^5.6.3",
"i18next": "^24.2.2",
"i18next-browser-languagedetector": "^8.1.0",
"i18next": "^25.3.2",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"linkify-plugin-mention": "^4.2.0",
"linkify-react": "^4.3.1",
"linkify-string": "^4.3.1",
"linkifyjs": "^4.3.1",
"linkify-plugin-mention": "^4.3.2",
"linkify-react": "^4.3.2",
"linkify-string": "^4.3.2",
"linkifyjs": "^4.3.2",
"localforage": "^1.10.0",
"localforage-setitems": "^1.4.0",
"lodash": "^4.17.21",
"masonry-layout": "^4.2.2",
"mini-css-extract-plugin": "^2.9.2",
"mini-css-extract-plugin": "^2.9.3",
"prismjs": "^1.30.0",
"qrcode.react": "^4.2.0",
"react": "^19.1.0",
"react": "^19.1.1",
"react-dev-utils": "^12.0.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "^19.1.0",
"react-dom": "^19.1.1",
"react-error-boundary": "^6.0.0",
"react-hot-toast": "^2.5.2",
"react-i18next": "^15.5.1",
"react-i18next": "^15.6.1",
"react-redux": "^9.2.0",
"react-refresh": "0.17.0",
"react-router-dom": "^7.6.0",
"react-router-dom": "^7.8.0",
"react-syntax-highlighter": "^15.6.1",
"react-textarea-autosize": "^8.5.9",
"react-use-wizard": "^2.3.0",
"react-viewport-list": "^7.1.2",
"react-virtuoso": "^4.12.7",
"rooks": "^7.14.1",
"react-virtuoso": "^4.13.0",
"rooks": "^8.4.0",
"slate": "^0.112.0",
"slate-history": "^0.110.3",
"slate-hyperscript": "^0.100.0",
@@ -77,8 +77,8 @@
"tailwind-merge": "2.5.4",
"terser-webpack-plugin": "^5.3.14",
"tippy.js": "^6.3.7",
"wavesurfer.js": "^7.9.5",
"webpack": "^5.99.8",
"wavesurfer.js": "^7.10.1",
"webpack": "^5.101.0",
"webpack-dev-server": "^4.15.1",
"webpack-manifest-plugin": "^5.0.1",
"workbox-core": "^7.3.0",
@@ -86,7 +86,8 @@
"workbox-precaching": "^7.3.0",
"workbox-routing": "^7.3.0",
"workbox-strategies": "^7.3.0",
"workbox-webpack-plugin": "^7.3.0"
"workbox-webpack-plugin": "^7.3.0",
"yet-another-react-lightbox": "^3.25.0"
},
"scripts": {
"start": "REACT_APP_BUILD_TIME=$(date +%s) node scripts/start.js",
@@ -113,29 +114,30 @@
]
},
"devDependencies": {
"@babel/core": "^7.27.1",
"@eslint/js": "^9.26.0",
"@types/lodash": "^4.17.16",
"@babel/core": "^7.28.0",
"@eslint/js": "^9.32.0",
"@types/lodash": "^4.17.20",
"@types/masonry-layout": "^4.2.8",
"@types/node": "^22.15.18",
"@types/react": "18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react": "19.1.9",
"@types/react-dom": "^19.1.7",
"@types/react-helmet": "^6.1.11",
"@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.21",
"babel-loader": "^9.2.1",
"babel-plugin-named-asset-import": "^0.3.8",
"babel-preset-react-app": "^10.1.0",
"eslint": "^9.26.0",
"eslint": "^9.32.0",
"gh-pages": "^6.3.0",
"md5-file": "^5.0.0",
"postcss": "^8.5.3",
"postcss": "^8.5.6",
"postcss-loader": "^8.1.1",
"tailwindcss": "^3.4.14",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.1",
"typescript": "^5.9.2",
"typescript-eslint": "^8.39.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"packageManager": "pnpm@10.14.0",
"pnpm": {
"patchedDependencies": {
"slate-react": "patches/slate-react.patch",
+1514 -1782
View File
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -187,13 +187,20 @@ function build(previousFileSizes) {
}
function copyPublicFolder() {
const ignoreFiles = ["widget_demo.html", "CNAME", "email.tpl.html", "robots.txt"];
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: (file) => {
const [fileName] = file.split("/").slice(-1);
const filters = file !== paths.appHtml && !ignoreFiles.includes(fileName);
return filters;
},
filter: (file) => file !== paths.appHtml,
});
}
// function copyPublicFolder() {
// const ignoreFiles = ["widget_demo.html", "CNAME", "email.tpl.html", "robots.txt"];
// fs.copySync(paths.appPublic, paths.appBuild, {
// dereference: true,
// filter: (file) => {
// const [fileName] = file.split("/").slice(-1);
// const filters = file !== paths.appHtml && !ignoreFiles.includes(fileName);
// return filters;
// },
// });
// }
+2 -2
View File
@@ -12,8 +12,8 @@ const prices: Price[] = [
},
];
const official_dev = `https://dev.voce.chat`;
const local_dev = `http://localhost:3000`;
// const local_dev = `http://chat.jcdl369.top:3009`;
const local_dev = `http://localhost:3333`;
// const local_dev = `https://dev.voce.chat`;
const dev_origin = process.env.REACT_APP_OFFICIAL_DEMO ? official_dev : local_dev;
// const local_dev = `https://im.ttt.td`;
+31 -67
View File
@@ -1,10 +1,11 @@
import { FC, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Ring } from "@uiball/loaders";
import { useKey, useOutsideClick } from "rooks";
import Modal from "./Modal";
import { FC } from "react";
import { Lightbox } from "yet-another-react-lightbox";
import "yet-another-react-lightbox/styles.css";
import Fullscreen from "yet-another-react-lightbox/plugins/fullscreen";
import Download from "yet-another-react-lightbox/plugins/download";
import Captions from "yet-another-react-lightbox/plugins/captions";
import Zoom from "yet-another-react-lightbox/plugins/zoom";
import "yet-another-react-lightbox/plugins/captions.css";
export interface PreviewImageData {
originUrl: string;
thumbnail?: string;
@@ -20,69 +21,32 @@ interface Props {
}
const ImagePreviewModal: FC<Props> = ({ download = true, data, closeModal }) => {
const { t } = useTranslation();
const [url, setUrl] = useState(data?.thumbnail);
const [loading, setLoading] = useState(true);
const wrapperRef = useRef<HTMLDivElement>(null);
useOutsideClick(wrapperRef, closeModal);
useKey(
"Escape",
() => {
closeModal();
},
{ eventTypes: ["keyup"] }
);
useEffect(() => {
if (data) {
const { originUrl } = data;
if (!originUrl) setLoading(false);
const img = new Image();
img.src = originUrl;
img.onload = () => {
setUrl(originUrl);
setLoading(false);
};
img.onerror = () => {
setLoading(false);
};
}
}, [data]);
if (!data) return null;
const { originUrl, downloadLink, name, type } = data;
return (
<Modal>
<div className="w-screen h-screen flex-center bg-black/90">
<div className={`relative flex flex-col justify-start gap-3`} ref={wrapperRef}>
<div className="overflow-auto">
<img
className={`max-w-[70vw] max-h-[80vh] ${loading ? "blur-sm" : ""}`}
src={url}
alt="preview"
/>
</div>
{download && (
<a
// onClick={handleDownload}
className="font-semibold text-sm text-gray-600 hover:underline hover:text-white"
download={name}
type={type}
href={downloadLink || originUrl}
rel="noreferrer"
>
{t("action.download_origin")}
</a>
)}
{loading && (
<div className="absolute top-[40%] left-1/2 -translate-x-1/2">
<Ring />
</div>
)}
</div>
</div>
</Modal>
<Lightbox
open
close={closeModal}
styles={{
navigationNext: {
display: "none",
},
navigationPrev: {
display: "none",
},
}}
slides={[
{
src: originUrl,
alt: name,
title: name,
description: name,
download: downloadLink || originUrl,
},
]}
plugins={download ? [Captions, Fullscreen, Zoom, Download] : [Captions, Fullscreen, Zoom]}
captions={{ descriptionTextAlign: "center" }}
/>
);
};
@@ -1,17 +1,14 @@
import { forwardRef } from "react";
import { PlateElement, PlateElementProps } from "@udecode/plate-common";
import { cn } from "@/utils";
const ParagraphElement = forwardRef<React.ElementRef<typeof PlateElement>, PlateElementProps>(
({ className, children, ...props }: PlateElementProps, ref) => {
return (
<PlateElement ref={ref} className={cn("m-0 px-0 py-1", className)} {...props}>
{children}
</PlateElement>
);
}
);
const ParagraphElement = ({ className, children, ref, ...props }: PlateElementProps) => {
return (
<PlateElement ref={ref} className={cn("m-0 px-0 py-1", className)} {...props}>
{children}
</PlateElement>
);
};
ParagraphElement.displayName = "ParagraphElement";
export { ParagraphElement };
@@ -1,9 +1,7 @@
import { forwardRef } from "react";
// @ts-ignore
const CustomList = forwardRef(({ style, ...props }, ref) => {
const CustomList = ({ style, ref, ...props }) => {
// @ts-ignore
return <div style={{ ...style, width: `calc(100% - 2rem)` }} {...props} ref={ref} />;
});
};
export default CustomList;