refactor: image preview
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import ImagePreviewModal, { PreviewImageData } from "../../common/component/ImagePreviewModal";
|
||||||
|
type Props = {
|
||||||
|
context?: "chat" | "markdown",
|
||||||
|
container: HTMLElement | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImagePreview = ({ container, context = "chat" }: Props) => {
|
||||||
|
const [previewImage, setPreviewImage] = useState<PreviewImageData | null>(null);
|
||||||
|
const closePreviewModal = () => {
|
||||||
|
setPreviewImage(null);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (!container) return;
|
||||||
|
const chatHandler = (evt: MouseEvent) => {
|
||||||
|
const target = evt.target as HTMLImageElement;
|
||||||
|
if (!target) return;
|
||||||
|
if (target.nodeName == "IMG" && target.classList.contains("preview")) {
|
||||||
|
const thumbnail = target.src;
|
||||||
|
const originUrl = target.dataset.origin || target.src;
|
||||||
|
const downloadLink = target.dataset.download || target.src;
|
||||||
|
const meta = JSON.parse(target.dataset.meta || "{}");
|
||||||
|
setPreviewImage({ thumbnail, originUrl, downloadLink, ...meta });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const markdownHandler = (evt: MouseEvent) => {
|
||||||
|
evt.stopPropagation();
|
||||||
|
const target = evt.target as HTMLImageElement;
|
||||||
|
if (!target) return;
|
||||||
|
// 图片
|
||||||
|
if (target.nodeName == "IMG") {
|
||||||
|
const urlObj = new URL(target.src);
|
||||||
|
const originUrl = `${urlObj.origin}${urlObj.pathname}?file_path=${urlObj.searchParams.get(
|
||||||
|
"file_path"
|
||||||
|
)}`;
|
||||||
|
const data = { originUrl };
|
||||||
|
setPreviewImage(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = context == 'chat' ? chatHandler : markdownHandler;
|
||||||
|
// 点击查看大图
|
||||||
|
container.addEventListener("click", handler, true);
|
||||||
|
return () => {
|
||||||
|
container.removeEventListener("click", handler, true);
|
||||||
|
};
|
||||||
|
}, [container, context]);
|
||||||
|
return previewImage ? <ImagePreviewModal download={context == "chat"} data={previewImage} closeModal={closePreviewModal} /> : null;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImagePreview;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState, useRef, FC } from "react";
|
import { useRef, FC } from "react";
|
||||||
import "prismjs/themes/prism.css";
|
import "prismjs/themes/prism.css";
|
||||||
import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';
|
import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';
|
||||||
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
|
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
|
||||||
@@ -6,8 +6,8 @@ import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin
|
|||||||
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
|
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
|
||||||
import { Viewer } from "@toast-ui/react-editor";
|
import { Viewer } from "@toast-ui/react-editor";
|
||||||
|
|
||||||
import ImagePreviewModal, { PreviewImageData } from "./ImagePreviewModal";
|
|
||||||
import { isDarkMode } from "../utils";
|
import { isDarkMode } from "../utils";
|
||||||
|
import ImagePreview from "./ImagePreview";
|
||||||
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@@ -15,42 +15,9 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
const MarkdownRender: FC<IProps> = ({ content }) => {
|
const MarkdownRender: FC<IProps> = ({ content }) => {
|
||||||
const mdContainer = useRef<HTMLDivElement | null>(null);
|
const mdContainer = useRef<HTMLDivElement | null>(null);
|
||||||
const [previewImage, setPreviewImage] = useState<PreviewImageData | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const container = mdContainer?.current;
|
|
||||||
if (!container) return;
|
|
||||||
// 点击查看大图
|
|
||||||
// todo: 事件代理
|
|
||||||
container.addEventListener(
|
|
||||||
"click",
|
|
||||||
(evt) => {
|
|
||||||
evt.stopPropagation();
|
|
||||||
const target = evt.target as HTMLImageElement;
|
|
||||||
if (!target) return;
|
|
||||||
// 图片
|
|
||||||
if (target.nodeName == "IMG") {
|
|
||||||
const urlObj = new URL(target.src);
|
|
||||||
const originUrl = `${urlObj.origin}${urlObj.pathname}?file_path=${urlObj.searchParams.get(
|
|
||||||
"file_path"
|
|
||||||
)}`;
|
|
||||||
const data = { originUrl };
|
|
||||||
setPreviewImage(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const closePreviewModal = () => {
|
|
||||||
setPreviewImage(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{previewImage && (
|
<ImagePreview container={mdContainer.current} context="markdown" />
|
||||||
<ImagePreviewModal download={false} data={previewImage} closeModal={closePreviewModal} />
|
|
||||||
)}
|
|
||||||
<div ref={mdContainer} id="MARKDOWN_CONTAINER">
|
<div ref={mdContainer} id="MARKDOWN_CONTAINER">
|
||||||
<Viewer initialValue={content} plugins={[codeSyntaxHighlight]} theme={isDarkMode() ? "dark" : "light"}></Viewer>
|
<Viewer initialValue={content} plugins={[codeSyntaxHighlight]} theme={isDarkMode() ? "dark" : "light"}></Viewer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useState, useRef, useEffect, FC, ReactElement } from "react";
|
import { useRef, useEffect, FC, ReactElement } from "react";
|
||||||
import { useDrop } from "react-dnd";
|
import { useDrop } from "react-dnd";
|
||||||
import { NativeTypes } from "react-dnd-html5-backend";
|
import { NativeTypes } from "react-dnd-html5-backend";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
import ImagePreviewModal from "../../../common/component/ImagePreviewModal";
|
|
||||||
import Send from "../../../common/component/Send";
|
import Send from "../../../common/component/Send";
|
||||||
import Operations from "./Operations";
|
import Operations from "./Operations";
|
||||||
import useUploadFile from "../../../common/hook/useUploadFile";
|
import useUploadFile from "../../../common/hook/useUploadFile";
|
||||||
@@ -14,6 +14,7 @@ import useLicense from "../../../common/hook/useLicense";
|
|||||||
import LicenseUpgradeTip from "./LicenseOutdatedTip";
|
import LicenseUpgradeTip from "./LicenseOutdatedTip";
|
||||||
import DnDTip from "./DnDTip";
|
import DnDTip from "./DnDTip";
|
||||||
import IconWarning from '../../../assets/icons/warning.svg';
|
import IconWarning from '../../../assets/icons/warning.svg';
|
||||||
|
import ImagePreview from "../../../common/component/ImagePreview";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
@@ -40,7 +41,7 @@ const Layout: FC<Props> = ({
|
|||||||
const { reachLimit } = useLicense();
|
const { reachLimit } = useLicense();
|
||||||
const { addStageFile } = useUploadFile({ context, id: to });
|
const { addStageFile } = useUploadFile({ context, id: to });
|
||||||
const messagesContainer = useRef<HTMLDivElement>(null);
|
const messagesContainer = useRef<HTMLDivElement>(null);
|
||||||
const [previewImage, setPreviewImage] = useState(null);
|
|
||||||
const { selects, channelsData, usersData, inputMode } = useAppSelector((store) => {
|
const { selects, channelsData, usersData, inputMode } = useAppSelector((store) => {
|
||||||
return {
|
return {
|
||||||
inputMode: store.ui.inputMode,
|
inputMode: store.ui.inputMode,
|
||||||
@@ -86,40 +87,15 @@ const Layout: FC<Props> = ({
|
|||||||
addStageFile(filesData);
|
addStageFile(filesData);
|
||||||
}
|
}
|
||||||
}, [dropFiles]);
|
}, [dropFiles]);
|
||||||
|
|
||||||
const closePreviewModal = () => {
|
|
||||||
setPreviewImage(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const container = messagesContainer?.current;
|
|
||||||
if (!container) return;
|
|
||||||
// 点击查看大图
|
|
||||||
container.addEventListener(
|
|
||||||
"click",
|
|
||||||
(evt) => {
|
|
||||||
const target = evt.target as HTMLImageElement;
|
|
||||||
if (!target) return;
|
|
||||||
if (target.nodeName == "IMG" && target.classList.contains("preview")) {
|
|
||||||
const thumbnail = target.src;
|
|
||||||
const originUrl = target.dataset.origin || target.src;
|
|
||||||
const downloadLink = target.dataset.download || target.src;
|
|
||||||
const meta = JSON.parse(target.dataset.meta || "{}");
|
|
||||||
setPreviewImage({ thumbnail, originUrl, downloadLink, ...meta });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
const name = context == "channel" ? channelsData[to]?.name : usersData[to]?.name;
|
const name = context == "channel" ? channelsData[to]?.name : usersData[to]?.name;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{previewImage && <ImagePreviewModal data={previewImage} closeModal={closePreviewModal} />}
|
<ImagePreview container={messagesContainer.current} />
|
||||||
<section ref={drop} className={`relative h-full w-full rounded-r-2xl flex`}>
|
<section ref={drop} className={`relative h-full w-full rounded-r-2xl flex`}>
|
||||||
<main className="flex flex-col flex-1">
|
<main className="flex flex-col flex-1">
|
||||||
{header}
|
{header}
|
||||||
<div className="w-full h-full flex items-start justify-between relative" ref={messagesContainer}>
|
<div className="w-full h-full flex items-start justify-between relative" >
|
||||||
<div className="rounded-br-2xl flex flex-col absolute bottom-0 w-full h-full">
|
<div className="rounded-br-2xl flex flex-col absolute bottom-0 w-full h-full" ref={messagesContainer}>
|
||||||
{/* 消息流 */}
|
{/* 消息流 */}
|
||||||
<article id={`VOCECHAT_FEED_${context}_${to}`} className="w-full h-full px-1 md:px-4 py-4.5 overflow-x-hidden overflow-y-scroll">
|
<article id={`VOCECHAT_FEED_${context}_${to}`} className="w-full h-full px-1 md:px-4 py-4.5 overflow-x-hidden overflow-y-scroll">
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
Reference in New Issue
Block a user