refactor: lots updates

This commit is contained in:
zerosoul
2022-03-17 11:15:13 +08:00
parent 4bc9932d0f
commit f22c7a01cb
69 changed files with 4625 additions and 1640 deletions
@@ -0,0 +1,213 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import Modal from "../../../common/component/Modal";
import Button from "../../../common/component/styled/Button";
import Contact from "../../../common/component/Contact";
import StyledCheckbox from "../../../common/component/styled/Checkbox";
import { useAddMembersMutation } from "../../../app/services/channel";
import closeIcon from "../../../assets/icons/close.svg?url";
import toast from "react-hot-toast";
// import useFilteredUsers from "../../../common/hook/useFilteredUsers";
const Styled = styled.div`
padding: 16px;
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
border-radius: 8px;
background-color: #fff;
min-width: 410px;
> .head {
font-weight: 600;
font-size: 18px;
line-height: 28px;
color: #374151;
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
.close {
width: 12px;
height: 12px;
cursor: pointer;
}
}
> .filter {
width: 376px;
height: 40px;
background: rgba(0, 0, 0, 0.08);
border-radius: 4px;
padding: 6px 8px;
display: flex;
align-items: center;
margin-bottom: 12px;
.selects {
display: flex;
align-items: center;
gap: 5px;
width: 100%;
overflow: scroll;
white-space: nowrap;
&::-webkit-scrollbar {
width: 0; /* Remove scrollbar space */
height: 0; /* Remove scrollbar space */
background: transparent; /* Optional: just make scrollbar invisible */
}
.select {
padding: 4px 6px;
background: #52edff;
border-radius: 4px;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
gap: 5px;
.close {
cursor: pointer;
width: 12px;
height: 12px;
filter: invert(1);
}
}
}
/* .input{
background: none;
padding: ;
} */
}
.users {
display: flex;
flex-direction: column;
/* height: 260px; */
padding-bottom: 20px;
max-height: 364px;
overflow: scroll;
.user {
cursor: pointer;
display: flex;
align-items: center;
padding: 4px 8px;
/* margin: 0 4px; */
width: -webkit-fill-available;
border-radius: 8px;
&:hover {
background: rgba(116, 127, 141, 0.1);
}
> div {
width: 100%;
}
}
}
> .btn {
width: 100%;
margin-top: 16px;
font-weight: 500;
font-size: 14px;
line-height: 20px;
}
`;
export default function AddMemberModal({ uids = [], cid = null, closeModal }) {
const [
addMembers,
{ isLoading: isAdding, isSuccess },
] = useAddMembersMutation();
const [selects, setSelects] = useState([]);
const { channel, contactIds, contactData } = useSelector((store) => {
return {
channel: store.channels.byId[cid],
contactIds: store.contacts.ids,
contactData: store.contacts.byId,
};
});
useEffect(() => {
if (isSuccess) {
toast.success("Add members successfully!");
closeModal();
}
}, [isSuccess]);
const handleAddMembers = () => {
addMembers({ id: cid, members: selects });
};
// const { input, updateInput, contacts } = useFilteredUsers();
const toggleCheckMember = ({ currentTarget }) => {
const { uid } = currentTarget.dataset;
if (selects.includes(+uid)) {
setSelects((prevs) => {
return prevs.filter((id) => id != uid);
});
} else {
setSelects([...selects, +uid]);
}
};
// const handleFilterInput = (evt) => {
// updateInput(evt.target.value);
// };
if (!channel) return null;
console.log("selects", selects);
return (
<Modal>
<Styled>
<div className="head">
Add friends to #{channel.name}{" "}
<img onClick={closeModal} className="close" src={closeIcon} />
</div>
<div className="filter">
<ul className="selects">
{selects.map((uid) => {
return (
<li className="select" key={uid}>
{contactData[uid].name}
<img
data-uid={uid}
onClick={toggleCheckMember}
className="close"
src={closeIcon}
/>
</li>
);
})}
</ul>
{/* <input
type="text"
className="input"
value={input}
onChange={handleFilterInput}
/> */}
</div>
<ul className="users">
{contactIds.map((uid) => {
const added = uids.includes(uid);
return (
<li
key={uid}
data-uid={uid}
className="user"
onClick={added ? null : toggleCheckMember}
>
<StyledCheckbox
disabled={added}
readOnly
checked={added || selects.includes(uid)}
name="cb"
id="cb"
/>
<Contact uid={uid} interactive={false} />
</li>
);
})}
</ul>
<Button
disabled={selects.length == 0 || isAdding}
className="btn main"
onClick={handleAddMembers}
>
{isAdding ? `Adding` : "Add"} to #{channel.name}
</Button>
</Styled>
</Modal>
);
}
+97 -66
View File
@@ -1,37 +1,48 @@
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
// import dayjs from "dayjs";
import { useSelector } from "react-redux";
import useChatScroll from "../../../common/hook/useChatScroll";
import Message from "../../../common/component/Message";
import ChannelIcon from "../../../common/component/ChannelIcon";
import Send from "../../../common/component/Send";
// import { readMessage } from "../../../app/slices/message";
import Contact from "../../../common/component/Contact";
import Layout from "../Layout";
import { renderMessageFragment } from "../utils";
import alertIcon from "../../../assets/icons/alert.svg?url";
import peopleIcon from "../../../assets/icons/people.svg?url";
import pinIcon from "../../../assets/icons/pin.svg?url";
import addIcon from "../../../assets/icons/add.svg?url";
import {
StyledNotification,
// StyledNotification,
StyledContacts,
StyledChannelChat,
StyledHeader,
} from "./styled";
import AddMemberModal from "./AddMemberModal";
export default function ChannelChat({ cid = "", dropFiles = [] }) {
// const containerRef = useRef(null);
const [membersVisible, setMembersVisible] = useState(true);
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
const [dragFiles, setDragFiles] = useState([]);
// const dispatch = useDispatch();
const { msgIds, userIds, data } = useSelector((store) => {
const { msgIds, userIds, data, messageData } = useSelector((store) => {
return {
msgIds: store.channelMessage[cid] || [],
userIds: store.contacts.ids,
data: store.channels.byId[cid],
data: store.channels.byId[cid] || {},
messageData: store.message || {},
};
});
const ref = useChatScroll(msgIds);
// const handleClearUnreads = () => {
// dispatch(readMessage(msgIds));
// };
const toggleMembersVisible = () => {
setMembersVisible((prev) => !prev);
};
const toggleAddVisible = () => {
setAddMemberModalVisible((prev) => !prev);
};
useEffect(() => {
if (dropFiles.length) {
setDragFiles(dropFiles);
@@ -41,65 +52,84 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
const memberIds = members.length == 0 ? userIds : members;
console.log("channel message list", msgIds);
return (
<Layout
setDragFiles={setDragFiles}
// ref={containerRef}
header={
<StyledHeader>
<div className="txt">
<ChannelIcon personal={!is_public} />
<span className="title">{name}</span>
<span className="desc">{description}</span>
<>
{addMemberModalVisible && (
<AddMemberModal
cid={cid}
uids={members}
closeModal={toggleAddVisible}
/>
)}
<Layout
setDragFiles={setDragFiles}
// ref={containerRef}
header={
<StyledHeader>
<div className="txt">
<ChannelIcon personal={!is_public} />
<span className="title">{name}</span>
<span className="desc">{description}</span>
</div>
<ul className="opts">
<li className="opt">
<img src={alertIcon} alt="opt icon" />
</li>
<li className="opt">
<img src={pinIcon} alt="opt icon" />
</li>
<li className="opt" onClick={toggleMembersVisible}>
<img src={peopleIcon} alt="opt icon" />
</li>
</ul>
</StyledHeader>
}
contacts={
membersVisible ? (
<>
<StyledContacts>
{!is_public && (
<div className="add" onClick={toggleAddVisible}>
<img className="icon" src={addIcon} />
<div className="txt">Add members</div>
</div>
)}
{memberIds.map((uid) => {
return <Contact key={uid} uid={uid} popover />;
})}
</StyledContacts>
</>
) : null
}
>
<StyledChannelChat>
<div className="wrapper" ref={ref}>
<div className="info">
<h2 className="title">Welcome to #{name} !</h2>
<p className="desc">This is the start of the #{name} channel. </p>
{/* <button className="edit">Edit Channel</button> */}
</div>
<div className="chat">
{[...msgIds]
.sort((a, b) => {
return Number(a) - Number(b);
})
.map((mid, idx) => {
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
const curr = messageData[mid];
return renderMessageFragment({
prev,
curr,
contextId: cid,
context: "channel",
});
})}
</div>
</div>
<ul className="opts">
<li className="opt">
<img
src="https://static.nicegoodthings.com/project/rustchat/icon.alert.svg"
alt="opt icon"
/>
</li>
<li className="opt">
<img
src="https://static.nicegoodthings.com/project/rustchat/icon.pin.svg"
alt="opt icon"
/>
</li>
<li className="opt">
<img
src="https://static.nicegoodthings.com/project/rustchat/icon.people.svg"
alt="opt icon"
/>
</li>
</ul>
</StyledHeader>
}
contacts={
<StyledContacts>
{memberIds.map((uid) => {
return <Contact key={uid} uid={uid} popover />;
})}
</StyledContacts>
}
>
<StyledChannelChat>
<div className="wrapper" ref={ref}>
<div className="info">
<h2 className="title">Welcome to #{name} !</h2>
<p className="desc">This is the start of the #{name} channel. </p>
{/* <button className="edit">Edit Channel</button> */}
</div>
<div className="chat">
{msgIds.map((mid, idx) => {
// if (!msg) return null;
return <Message contextId={cid} mid={mid} key={idx} />;
})}
</div>
</div>
<Send dragFiles={dragFiles} id={cid} type="channel" name={name} />
<div className="placeholder"></div>
</StyledChannelChat>
{/* {unreads != 0 && (
<Send dragFiles={dragFiles} id={cid} type="channel" name={name} />
<div className="placeholder"></div>
</StyledChannelChat>
{/* {unreads != 0 && (
<StyledNotification>
<div className="content">
{unreads} new messages
@@ -112,6 +142,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
</button>
</StyledNotification>
)} */}
</Layout>
</Layout>
</>
);
}
+23
View File
@@ -72,6 +72,29 @@ export const StyledContacts = styled.div`
overflow-y: scroll;
background: #f5f6f7;
padding: 8px;
> .add {
cursor: pointer;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 4px;
padding: 10px;
border-radius: 8px;
user-select: none;
&:hover {
background: rgba(116, 127, 141, 0.1);
}
.icon {
width: 24px;
height: 24px;
}
.txt {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #52525b;
}
}
`;
export const StyledChannelChat = styled.article`
position: relative;
+1 -1
View File
@@ -7,7 +7,7 @@ import useContextMenu from "../../common/hook/useContextMenu";
import ContextMenu from "../../common/component/ContextMenu";
import { toggleChannelSetting } from "../../app/slices/ui";
import ChannelIcon from "../../common/component/ChannelIcon";
import getUnreadCount from "./getUnreadCount";
import { getUnreadCount } from "./utils";
const NavItem = ({ id, setFiles, contextMenuEventHandler }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
+28 -27
View File
@@ -1,21 +1,24 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import addIcon from "../../../assets/icons/add.person.svg?url";
import callIcon from "../../../assets/icons/call.svg?url";
import videoIcon from "../../../assets/icons/video.svg?url";
import useChatScroll from "../../../common/hook/useChatScroll";
import Message from "../../../common/component/Message";
import Send from "../../../common/component/Send";
import Contact from "../../../common/component/Contact";
import Layout from "../Layout";
import { StyledHeader, StyledDMChat } from "./styled";
import { renderMessageFragment } from "../utils";
export default function DMChat({ uid = "", dropFiles = [] }) {
console.log("dm files", dropFiles);
const [dragFiles, setDragFiles] = useState([]);
// const [mids, setMids] = useState([]);
const { msgIds, currUser } = useSelector((store) => {
const { msgIds, currUser, messageData, readUsers } = useSelector((store) => {
return {
currUser: store.contacts.byId[uid],
msgIds: store.userMessage.byId[uid] || [],
messageData: store.message,
readUsers: store.footprint.readUser || {},
};
});
const ref = useChatScroll(msgIds);
@@ -30,6 +33,7 @@ export default function DMChat({ uid = "", dropFiles = [] }) {
if (!currUser) return null;
// console.log("user msgs", msgs);
const readIndex = readUsers[uid] || 0;
return (
<Layout
setDragFiles={setDragFiles}
@@ -38,28 +42,13 @@ export default function DMChat({ uid = "", dropFiles = [] }) {
<Contact interactive={false} uid={currUser.uid} />
<ul className="opts">
<li className="opt">
<img
src="https://static.nicegoodthings.com/project/rustchat/icon.call.svg"
alt="opt icon"
/>
<img src={callIcon} alt="opt icon" />
</li>
<li className="opt">
<img
src="https://static.nicegoodthings.com/project/rustchat/icon.video.svg"
alt="opt icon"
/>
<img src={videoIcon} alt="opt icon" />
</li>
<li className="opt">
<img
src="https://static.nicegoodthings.com/project/rustchat/icon.people.add.svg"
alt="opt icon"
/>
</li>
<li className="opt">
<img
src="https://static.nicegoodthings.com/project/rustchat/icon.mark.read.svg"
alt="opt icon"
/>
<img src={addIcon} alt="opt icon" />
</li>
</ul>
</StyledHeader>
@@ -67,11 +56,23 @@ export default function DMChat({ uid = "", dropFiles = [] }) {
>
<StyledDMChat>
<div className="chat" ref={ref}>
{msgIds.map((mid) => {
// if (!msg) return null;
// console.log("user msg", msg);
return <Message mid={mid} key={mid} contextId={uid} />;
})}
{[...msgIds]
.sort((a, b) => {
return Number(a) - Number(b);
})
.map((mid, idx) => {
const curr = messageData[mid];
const self = curr.from_uid == currUser.uid;
const read = self ? true : mid <= readIndex ? true : false;
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
return renderMessageFragment({
prev,
curr,
contextId: uid,
read,
context: "user",
});
})}
</div>
</StyledDMChat>
<div className="placeholder"></div>
+7 -2
View File
@@ -5,6 +5,7 @@ import relativeTime from "dayjs/plugin/relativeTime";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import { useSelector } from "react-redux";
import { renderPreviewMessage } from "./utils";
import Contact from "../../common/component/Contact";
dayjs.extend(relativeTime);
const NavItem = ({ uid, mid, unreads, setFiles }) => {
@@ -48,8 +49,12 @@ const NavItem = ({ uid, mid, unreads, setFiles }) => {
</div>
<div className="down">
{currMsg && <div className="msg">{currMsg.content}</div>}
{unreads > 0 && <i className="badge">{unreads}</i>}
{renderPreviewMessage(currMsg)}
{unreads > 0 && (
<i className={`badge ${unreads > 99 ? "dot" : ""}`}>
{unreads > 99 ? null : unreads}
</i>
)}
</div>
</div>
</NavLink>
+1 -1
View File
@@ -14,7 +14,7 @@ const StyledWrapper = styled.article`
padding: 0 20px;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
}
.main {
> .main {
height: calc(100vh - 56px);
width: 100%;
display: flex;
-24
View File
@@ -1,24 +0,0 @@
// import React from "react";
import { VariableSizeList as List } from "react-window";
import Message from "../../common/component/Message";
export default function MessageList({ messages = [] }) {
const Row = ({ index, style }) => (
<div style={style}>
<Message {...messages[index]} />
</div>
);
const getItemSize = (index) =>
messages[index].content_type.startsWith("image") ? 150 : 56;
return (
<List
height={window.innerHeight - 56}
width={"100%"}
itemCount={messages.length}
itemSize={getItemSize}
// estimatedItemSize={56}
>
{Row}
</List>
);
}
-12
View File
@@ -1,12 +0,0 @@
const getUnreadCount = (mids, messageData) => {
if (!mids || !messageData) return 0;
let unreads = 0;
mids.forEach((id) => {
if (!messageData[id].read) {
unreads++;
}
});
return unreads;
};
export default getUnreadCount;
+1 -5
View File
@@ -16,7 +16,7 @@ import ChannelList from "./ChannelList";
import ContactsModal from "../../common/component/ContactsModal";
import ChannelModal from "../../common/component/ChannelModal";
import DMList from "./DMList";
import getUnreadCount from "./getUnreadCount";
import { getUnreadCount } from "./utils";
export default function ChatPage() {
const [channelDropFiles, setChannelDropFiles] = useState([]);
@@ -37,10 +37,6 @@ export default function ChatPage() {
const toggleChannelModalVisible = () => {
setChannelModalVisible((prev) => !prev);
};
// const getUnreadCount = (gid) => {
// return Object.values(ChannelMsgData[gid] || {}).filter((m) => m.read)
// .length;
// };
const handleToggleExpand = (evt) => {
const { currentTarget } = evt;
const listEle = currentTarget.parentElement.parentElement;
+85
View File
@@ -0,0 +1,85 @@
import React from "react";
import dayjs from "dayjs";
import { ContentTypes } from "../../app/config";
import Divider from "../../common/component/Divider";
import Message from "../../common/component/Message";
export const getUnreadCount = (mids, messageData) => {
if (!mids || !messageData) return 0;
let unreads = 0;
mids.forEach((id) => {
if (messageData[id] && !messageData[id].read) {
unreads++;
}
});
return unreads;
};
export const renderPreviewMessage = (message = null) => {
if (!message) return null;
const { content_type, content } = message;
let res = null;
switch (content_type) {
case ContentTypes.text:
{
res = <div className="msg">{content}</div>;
}
break;
case ContentTypes.imageJPG:
case ContentTypes.image:
{
res = <div className="msg">[image]</div>;
}
break;
case ContentTypes.markdown:
{
res = <div className="msg">[markdown]</div>;
}
break;
case ContentTypes.file:
{
res = <div className="msg">[file]</div>;
}
break;
default:
break;
}
return res;
};
export const renderMessageFragment = ({
prev = null,
curr = null,
contextId = 0,
read = true,
context = "user",
}) => {
if (!curr) return null;
let { created_at, mid } = curr;
let divider = null;
let time = dayjs(created_at).format("YYYY/MM/DD");
if (!prev) {
divider = time;
} else {
let { created_at: prev_created_at } = prev;
if (!dayjs(prev_created_at).isSame(created_at, "day")) {
divider = time;
}
}
return (
<React.Fragment key={mid}>
{divider && <Divider content={divider}></Divider>}
<Message
read={read}
context={context}
mid={mid}
key={mid}
contextId={contextId}
/>
</React.Fragment>
);
};
export default getUnreadCount;