feat: select multiple messages and forward
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useKey } from "rooks";
|
||||
import { updateSelectMessages } from "../../../app/slices/ui";
|
||||
import IconForward from "../../../assets/icons/forward.svg";
|
||||
import IconBookmark from "../../../assets/icons/bookmark.svg";
|
||||
import IconDelete from "../../../assets/icons/delete.svg";
|
||||
import IconClose from "../../../assets/icons/close.circle.svg";
|
||||
import ForwardModal from "../../../common/component/ForwardModal";
|
||||
const Styled = styled.div`
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
/* padding-bottom: 0; */
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0px -1px 0px rgba(0, 0, 0, 0.05);
|
||||
.opt {
|
||||
padding: 8px;
|
||||
background: #f2f4f7;
|
||||
border-radius: var(--br);
|
||||
}
|
||||
.close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
`;
|
||||
export default function Operations({ context, id }) {
|
||||
const mids = useSelector(
|
||||
(store) => store.ui.selectMessages[`${context}_${id}`]
|
||||
);
|
||||
const [forwardModalVisible, setForwardModalVisible] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const handleClose = () => {
|
||||
dispatch(updateSelectMessages({ context, id, operation: "reset" }));
|
||||
};
|
||||
const toggleForwardModal = () => {
|
||||
setForwardModalVisible((prev) => !prev);
|
||||
};
|
||||
useKey("Escape", (evt) => {
|
||||
console.log("Escape keypress", evt);
|
||||
dispatch(updateSelectMessages({ context, id, operation: "reset" }));
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Styled>
|
||||
<button className="opt" onClick={toggleForwardModal}>
|
||||
<IconForward />
|
||||
</button>
|
||||
<button className="opt">
|
||||
<IconBookmark />
|
||||
</button>
|
||||
<button className="opt">
|
||||
<IconDelete />
|
||||
</button>
|
||||
<IconClose className="close" onClick={handleClose} />
|
||||
</Styled>
|
||||
{forwardModalVisible && (
|
||||
<ForwardModal mids={mids} closeModal={toggleForwardModal} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { NativeTypes } from "react-dnd-html5-backend";
|
||||
import { updateUploadFiles } from "../../../app/slices/ui";
|
||||
import ImagePreviewModal from "../../../common/component/ImagePreviewModal";
|
||||
import Send from "../../../common/component/Send";
|
||||
import Styled from "./styled";
|
||||
import Operations from "./Operations";
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
header,
|
||||
aside = null,
|
||||
contacts = null,
|
||||
dropFiles = [],
|
||||
context = "channel",
|
||||
to = null,
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const messagesContainer = useRef(null);
|
||||
const [previewImage, setPreviewImage] = useState(null);
|
||||
const selects = useSelector(
|
||||
(store) => store.ui.selectMessages[`${context}_${to}`]
|
||||
);
|
||||
const [{ isActive }, drop] = useDrop(
|
||||
() => ({
|
||||
accept: [NativeTypes.FILE],
|
||||
drop({ files }) {
|
||||
console.log("drop files", files, context, to);
|
||||
if (files.length) {
|
||||
const filesData = files.map((file) => {
|
||||
const { size, type, name } = file;
|
||||
const url = URL.createObjectURL(file);
|
||||
return { size, type, name, url };
|
||||
});
|
||||
dispatch(updateUploadFiles({ context, id: to, data: filesData }));
|
||||
}
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isActive: monitor.canDrop() && monitor.isOver(),
|
||||
}),
|
||||
}),
|
||||
[context, to]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (dropFiles?.length) {
|
||||
const filesData = dropFiles.map((file) => {
|
||||
const { size, type, name } = file;
|
||||
const url = URL.createObjectURL(file);
|
||||
return { size, type, name, url };
|
||||
});
|
||||
dispatch(updateUploadFiles({ context, id: to, data: filesData }));
|
||||
}
|
||||
}, [dropFiles]);
|
||||
|
||||
const closePreviewModal = () => {
|
||||
setPreviewImage(null);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (messagesContainer) {
|
||||
const container = messagesContainer.current;
|
||||
// 点击查看大图
|
||||
container.addEventListener(
|
||||
"click",
|
||||
(evt) => {
|
||||
console.log(evt);
|
||||
const { target } = evt;
|
||||
if (
|
||||
target.nodeName == "IMG" &&
|
||||
target.classList.contains("preview")
|
||||
) {
|
||||
const originUrl = target.dataset.origin || target.src;
|
||||
const downloadLink = target.dataset.download || target.src;
|
||||
const meta = JSON.parse(target.dataset.meta || "{}");
|
||||
setPreviewImage({ originUrl, downloadLink, ...meta });
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{previewImage && (
|
||||
<ImagePreviewModal data={previewImage} closeModal={closePreviewModal} />
|
||||
)}
|
||||
<Styled ref={drop}>
|
||||
{header}
|
||||
<main className="main" ref={messagesContainer}>
|
||||
<div className="chat">
|
||||
{children}
|
||||
<div className={`send ${selects ? "selecting" : ""}`}>
|
||||
<Send key={to} id={to} context={context} />
|
||||
{selects && <Operations context={context} id={to} />}
|
||||
</div>
|
||||
</div>
|
||||
{contacts && <div className="members">{contacts}</div>}
|
||||
{aside && <div className="aside">{aside}</div>}
|
||||
</main>
|
||||
<div
|
||||
className={`drag_tip ${
|
||||
isActive ? "visible animate__animated animate__fadeIn" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`box ${
|
||||
isActive ? "animate__animated animate__bounceIn" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="inner">
|
||||
<h4 className="head">Upload to #Channel</h4>
|
||||
<span className="intro">
|
||||
Photos accept jpg, png, max size limit to 10M.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Styled>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import styled from "styled-components";
|
||||
const Styled = styled.article`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-top-right-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
> .head {
|
||||
box-sizing: content-box;
|
||||
height: 56px;
|
||||
padding: 0 20px;
|
||||
/* box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1); */
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
> .main {
|
||||
height: calc(100vh - 56px - 22px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
> .chat {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
/* padding-bottom: 16px; */
|
||||
height: calc(100vh - 56px - 22px);
|
||||
> .send {
|
||||
padding: 0 16px 16px 16px;
|
||||
&.selecting {
|
||||
padding: 0;
|
||||
> .send {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.members {
|
||||
box-shadow: inset 0px 10px 2px -10px rgba(0, 0, 0, 0.1);
|
||||
/* margin-top: 1px; */
|
||||
/* border-top: 1px solid transparent; */
|
||||
}
|
||||
> .aside {
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -56px;
|
||||
transform: translateX(100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.divider {
|
||||
border: none;
|
||||
background-color: #d4d4d4;
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
margin: 18px auto;
|
||||
}
|
||||
.tools,
|
||||
.apps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tools {
|
||||
gap: 24px;
|
||||
.tool {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.apps {
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.drag_tip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
visibility: hidden;
|
||||
/* pointer-events: none; */
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
.box {
|
||||
padding: 16px;
|
||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||
border-radius: 8px;
|
||||
background: #52edff;
|
||||
.inner {
|
||||
padding: 16px;
|
||||
padding-top: 64px;
|
||||
border: 2px dashed #a5f3fc;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
.head {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.intro {
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default Styled;
|
||||
Reference in New Issue
Block a user