feat: select multiple messages and forward

This commit is contained in:
zerosoul
2022-04-26 11:29:03 +08:00
parent f5ce856061
commit fbedaa8825
24 changed files with 702 additions and 329 deletions
+68
View File
@@ -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} />
)}
</>
);
}
+125
View File
@@ -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>
</>
);
}
+119
View File
@@ -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;