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