Files
ColdBreeze-chat-web/src/routes/chat/Layout/index.tsx
T
2023-05-05 15:58:43 +08:00

133 lines
4.5 KiB
TypeScript

import { useRef, useEffect, FC, ReactElement } from "react";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import clsx from "clsx";
import { toast } from "react-hot-toast";
import Send from "../../../common/component/Send";
import Operations from "./Operations";
import useUploadFile from "../../../common/hook/useUploadFile";
import { useAppSelector } from "../../../app/store";
import LoginTip from "./LoginTip";
import useLicense from "../../../common/hook/useLicense";
import LicenseUpgradeTip from "./LicenseOutdatedTip";
import DnDTip from "./DnDTip";
import IconWarning from '../../../assets/icons/warning.svg';
import ImagePreview from "../../../common/component/ImagePreview";
import VirtualMessageFeed from "./VirtualMessageFeed";
interface Props {
readonly?: boolean;
header: ReactElement;
aside?: ReactElement | null;
users?: ReactElement | null;
voice?: ReactElement | null;
dropFiles?: File[];
context: "channel" | "user";
to: number;
}
const Layout: FC<Props> = ({
readonly = false,
header,
aside = null,
users = null,
voice = null,
dropFiles = [],
context = "channel",
to
}) => {
// const { t } = useTranslation('chat');
const { reachLimit } = useLicense();
const { addStageFile } = useUploadFile({ context, id: to });
const messagesContainer = useRef<HTMLDivElement>(null);
const { selects, channelsData, usersData, inputMode } = useAppSelector((store) => {
return {
inputMode: store.ui.inputMode,
selects: store.ui.selectMessages[`${context}_${to}`],
channelsData: store.channels.byId,
usersData: store.users.byId
};
});
const [{ isActive }, drop] = useDrop(
() => ({
accept: [NativeTypes.FILE],
drop({ files }) {
// console.log("iii", inputMode);
if (inputMode !== "text") {
toast("DnD not allowed in this input mode", {
icon: <IconWarning className="w-5 h-5" />,
});
return;
};
if (files.length) {
const filesData = files.map((file) => {
const { size, type, name } = file;
const url = URL.createObjectURL(file);
return { size, type, name, url };
});
addStageFile(filesData);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver()
})
}),
[context, to, inputMode]
);
useEffect(() => {
if (dropFiles?.length) {
const filesData = dropFiles.map((file) => {
const { size, type, name } = file;
const url = URL.createObjectURL(file);
return { size, type, name, url };
});
addStageFile(filesData);
}
}, [dropFiles]);
const name = context == "channel" ? channelsData[to]?.name : usersData[to]?.name;
return (
<>
<ImagePreview container={messagesContainer.current} />
<section ref={drop} className={`relative h-full w-full rounded-r-2xl flex`}>
<main className="flex flex-col flex-1">
{header}
<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" ref={messagesContainer}>
{/* 消息流 */}
<VirtualMessageFeed key={`${context}_${to}`} context={context} id={to} />
{/* 发送框 */}
<div className={`px-2 py-0 md:p-4 ${selects ? "selecting" : ""}`}>
{readonly ? (
<LoginTip />
) : reachLimit ? (
<LicenseUpgradeTip />
) : (
<div className={clsx(`flex justify-center`, selects && "hidden")}>
<Send key={to} id={to} context={context} />
</div>
)}
{selects && <Operations context={context} id={to} />}
</div>
</div>
</div>
</main>
{aside && <div className={clsx("flex z-50 p-3 absolute right-0 top-14 md:right-0 md:top-0 md:translate-x-full flex-col")}>
{aside}
</div>}
{users && <div className="hidden md:block">{users}</div>}
{voice && <div className="max-md:absolute max-md:right-11 max-md:top-14 max-md:h-fit max-md:overflow-y-scroll bg-white dark:!bg-gray-700 md:block">{voice}</div>}
{!readonly && inputMode == "text" && isActive && (
<DnDTip context={context} name={name} />
)}
</section >
</>
);
};
export default Layout;