feat: lots updates

This commit is contained in:
zerosoul
2022-01-30 21:30:12 +08:00
parent e18c7323a6
commit ceb02d834f
38 changed files with 1943 additions and 1832 deletions
-186
View File
@@ -1,186 +0,0 @@
import { useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import Message from "../../common/component/Message";
import ChannelIcon from "../../common/component/ChannelIcon";
import Send from "../../common/component/Send";
import { useGetContactsQuery } from "../../app/services/contact";
// import msgs from "./channel.msg.mock.json";
import Contact from "../../common/component/Contact";
const StyledWrapper = styled.article`
position: relative;
width: 100%;
background: #fff;
height: 100vh;
overflow-y: scroll;
overflow-x: hidden;
.head {
height: 56px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
.txt {
display: flex;
align-items: center;
gap: 5px;
.title {
font-size: 16px;
line-height: 24px;
color: #1c1c1e;
}
.desc {
margin-left: 8px;
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #616161;
}
}
}
.main {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
padding: 0 0 20px 16px;
.notification {
padding: 3px 8px;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 18px;
color: #fff;
position: absolute;
top: 0;
left: 10px;
width: 900px;
height: 24px;
background: linear-gradient(135deg, #3c8ce7 0%, #00eaff 100%);
border-radius: 0px 0px 8px 8px;
display: flex;
align-items: center;
justify-content: space-between;
.clear {
cursor: pointer;
color: inherit;
border: none;
background: none;
outline: none;
}
}
.channel {
width: 684px;
.info {
padding-top: 114px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
.title {
font-weight: bold;
font-size: 36px;
line-height: 44px;
}
.desc {
color: #78787c;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
.edit {
color: #3c8ce7;
padding: 0;
border: none;
outline: none;
background: none;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
}
.chat {
padding: 18px 0;
}
}
.contacts {
display: flex;
flex-direction: column;
gap: 5px;
/* todo */
width: 226px;
height: calc(100vh - 56px);
overflow-y: scroll;
background: #f5f6f7;
padding: 16px;
}
}
`;
export default function ChannelChat({ cid = "", data = {} }) {
const msgs = useSelector((store) => {
return store.channelMsg[cid] || {};
});
const { data: users } = useGetContactsQuery();
useEffect(() => {
console.log({ cid });
}, [cid]);
const { name, description, is_public, members = [] } = data;
const filteredUsers =
members.length == 0
? users
: users.filter((u) => {
return members.includes(u.uid);
});
console.log("channel message list", msgs);
return (
<StyledWrapper>
<header className="head">
<div className="txt">
<ChannelIcon personal={!is_public} />
<span className="title">{name}</span>
<span className="desc">{description}</span>
</div>
<ul className="members">members</ul>
</header>
<main className="main">
<div className="notification">
<div className="content">25+ new messages since 3:24 PM</div>
<button className="clear">Mark As Read</button>
</div>
<div className="channel">
<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">
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
const { from_uid, content, created_at } = msg;
return (
<Message
key={mid}
time={created_at}
uid={from_uid}
content={content}
/>
);
})}
</div>
</div>
<div className="contacts">
{filteredUsers.map(({ name, status, uid }) => {
return <Contact key={name} uid={uid} status={status} />;
})}
</div>
</main>
<Send id={cid} type="channel" name={name} />
</StyledWrapper>
);
}
+110
View File
@@ -0,0 +1,110 @@
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import dayjs from "dayjs";
import Message from "../../../common/component/Message";
import ChannelIcon from "../../../common/component/ChannelIcon";
import Send from "../../../common/component/Send";
import {
clearChannelMsgUnread,
setLastAccessTime,
} from "../../../app/slices/message.channel";
import { useGetContactsQuery } from "../../../app/services/contact";
import Contact from "../../../common/component/Contact";
import Layout from "../Layout";
import {
StyledNotification,
StyledContacts,
StyledChannelChat,
StyledHeader,
} from "./styled";
export default function ChannelChat({ cid = "", unreads = 0, data = {} }) {
const dispatch = useDispatch();
const msgs = useSelector((store) => {
return store.channelMsg[cid] || {};
});
const { data: users } = useGetContactsQuery();
const handleClearUnreads = () => {
dispatch(clearChannelMsgUnread(cid));
};
useEffect(() => {
console.log({ cid });
return () => {
dispatch(setLastAccessTime(cid));
};
}, [cid]);
const { name, description, is_public, members = [] } = data;
const filteredUsers =
members.length == 0
? users
: users.filter((u) => {
return members.includes(u.uid);
});
console.log("channel message list", msgs);
return (
<Layout
header={
<StyledHeader>
<div className="txt">
<ChannelIcon personal={!is_public} />
<span className="title">{name}</span>
<span className="desc">{description}</span>
</div>
<ul className="members">members</ul>
</StyledHeader>
}
contacts={
<StyledContacts>
{filteredUsers.map(({ name, status, uid }) => {
return <Contact key={name} uid={uid} status={status} />;
})}
</StyledContacts>
}
>
<StyledChannelChat>
<div className="wrapper">
<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">
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
const { from_uid, content, created_at, unread } = msg;
return (
<Message
unread={unread}
gid={cid}
mid={mid}
key={mid}
time={created_at}
fromUid={from_uid}
content={content}
/>
);
})}
</div>
</div>
<Send id={cid} type="channel" name={name} />
<div className="placeholder"></div>
</StyledChannelChat>
{unreads != 0 && (
<StyledNotification>
<div className="content">
{unreads} new messages
{msgs.lastAccess
? `since ${dayjs(msgs.lastAccess).format("YYYY-MM-DD h:mm:ss A")}`
: ""}
</div>
<button onClick={handleClearUnreads} className="clear">
Mark As Read
</button>
</StyledNotification>
)}
</Layout>
);
}
+111
View File
@@ -0,0 +1,111 @@
import styled from "styled-components";
export const StyledHeader = styled.header`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.txt {
display: flex;
align-items: center;
gap: 5px;
.title {
font-size: 16px;
line-height: 24px;
color: #1c1c1e;
}
.desc {
margin-left: 8px;
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #616161;
}
}
`;
export const StyledNotification = styled.div`
padding: 3px 8px;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 18px;
color: #fff;
position: absolute;
top: 0;
left: 10px;
width: 900px;
height: 24px;
background: linear-gradient(135deg, #3c8ce7 0%, #00eaff 100%);
border-radius: 0px 0px 8px 8px;
display: flex;
align-items: center;
justify-content: space-between;
.clear {
cursor: pointer;
color: inherit;
border: none;
background: none;
outline: none;
}
`;
export const StyledContacts = styled.div`
display: flex;
flex-direction: column;
gap: 5px;
/* todo */
width: 226px;
height: calc(100vh - 56px);
overflow-y: scroll;
background: #f5f6f7;
padding: 16px;
`;
export const StyledChannelChat = styled.article`
position: relative;
width: 100%;
/* margin-bottom: 120px; */
> .wrapper {
display: flex;
flex-direction: column;
padding: 0 16px;
height: calc(100vh - 56px - 80px);
overflow-y: scroll;
overflow-x: visible;
.info {
padding-top: 114px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
.title {
font-weight: bold;
font-size: 36px;
line-height: 44px;
}
.desc {
color: #78787c;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
.edit {
color: #3c8ce7;
padding: 0;
border: none;
outline: none;
background: none;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
}
.chat {
height: -webkit-fill-available;
padding: 18px 0;
}
}
.placeholder {
width: 100%;
height: 80px;
}
`;
-94
View File
@@ -1,94 +0,0 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import Message from "../../common/component/Message";
import Send from "../../common/component/Send";
// import msgs from "./channel.msg.mock.json";
import Contact from "../../common/component/Contact";
import { useGetContactsQuery } from "../../app/services/contact";
const StyledWrapper = styled.article`
position: relative;
width: 100%;
background: #fff;
height: 100vh;
overflow-y: scroll;
overflow-x: hidden;
.head {
height: 56px;
padding: 0 20px 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
.txt {
display: flex;
align-items: center;
gap: 5px;
.title {
font-size: 16px;
line-height: 24px;
color: #1c1c1e;
}
.desc {
margin-left: 8px;
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #616161;
}
}
}
.main {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
padding: 0 0 20px 16px;
.chat {
width: 890px;
padding: 18px 0;
}
}
`;
export default function DMChat({ uid = "" }) {
const msgs = useSelector((store) => {
return store.userMsg[uid] || {};
});
const { data: contacts } = useGetContactsQuery();
const [currUser, setCurrUser] = useState(null);
useEffect(() => {
console.log({ uid });
if (uid && contacts) {
setCurrUser(contacts.find((c) => c.uid == uid));
}
}, [uid, contacts]);
if (!currUser) return null;
console.log("user msgs", msgs);
return (
<StyledWrapper>
<header className="head">
<Contact interactive={false} uid={currUser.uid} />
{/* <ul className="members">members</ul> */}
</header>
<main className="main">
<div className="chat">
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
const { from_uid, content, created_at } = msg;
return (
<Message
key={mid}
time={created_at}
uid={from_uid}
content={content}
/>
);
})}
</div>
</main>
<Send type="user" name={currUser?.name} id={currUser?.uid} />
</StyledWrapper>
);
}
+57
View File
@@ -0,0 +1,57 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import Message from "../../../common/component/Message";
import Send from "../../../common/component/Send";
import Contact from "../../../common/component/Contact";
import { useGetContactsQuery } from "../../../app/services/contact";
import Layout from "../Layout";
import { StyledHeader, StyledDMChat } from "./styled";
export default function DMChat({ uid = "" }) {
const msgs = useSelector((store) => {
return store.userMsg[uid] || {};
});
const { data: contacts } = useGetContactsQuery();
const [currUser, setCurrUser] = useState(null);
useEffect(() => {
console.log({ uid });
if (uid && contacts) {
setCurrUser(contacts.find((c) => c.uid == uid));
}
}, [uid, contacts]);
if (!currUser) return null;
console.log("user msgs", msgs);
return (
<Layout
header={
<StyledHeader>
<Contact interactive={false} uid={currUser.uid} />
</StyledHeader>
}
>
<StyledDMChat>
<div className="chat">
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
console.log("user msg", msg);
const { from_uid, content, created_at, unread } = msg;
return (
<Message
unread={unread}
fromUid={from_uid}
mid={mid}
key={mid}
time={created_at}
uid={uid}
content={content}
/>
);
})}
</div>
</StyledDMChat>
<div className="placeholder"></div>
<Send type="user" name={currUser?.name} id={currUser?.uid} />
</Layout>
);
}
+48
View File
@@ -0,0 +1,48 @@
import styled from "styled-components";
export const StyledHeader = styled.header`
width: 100%;
height: 100%;
/* padding: 0 20px 0 10px; */
display: flex;
align-items: center;
justify-content: space-between;
/* tricky */
> div {
padding-left: 4px;
}
.txt {
display: flex;
align-items: center;
gap: 5px;
.title {
font-size: 16px;
line-height: 24px;
color: #1c1c1e;
}
.desc {
margin-left: 8px;
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #616161;
}
}
`;
export const StyledDMChat = styled.article`
position: relative;
width: 100%;
padding-top: 25px;
/* margin-bottom: 120px; */
> .chat {
display: flex;
flex-direction: column;
padding: 0 16px;
height: calc(100vh - 56px - 80px);
overflow-y: scroll;
overflow-x: visible;
}
.placeholder {
width: 100%;
height: 80px;
}
`;
+36
View File
@@ -0,0 +1,36 @@
import React from "react";
import styled from "styled-components";
const StyledWrapper = styled.article`
position: relative;
width: 100%;
background: #fff;
height: 100vh;
.head {
height: 56px;
padding: 0 20px;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
}
.main {
height: calc(100vh - 56px);
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
.members {
border-top: 1px solid transparent;
}
}
`;
export default function Layout({ children, header, contacts = null }) {
return (
<StyledWrapper>
<header className="head">{header}</header>
<main className="main">
{children}
{contacts && <div className="members">{contacts}</div>}
</main>
</StyledWrapper>
);
}
+41 -26
View File
@@ -3,10 +3,9 @@ import { useState } from "react";
import { NavLink, useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import dayjs from "dayjs";
import { MdAdd } from "react-icons/md";
import { AiOutlineCaretDown } from "react-icons/ai";
import { useGetChannelsQuery } from "../../app/services/channel";
import { useGetContactsQuery } from "../../app/services/contact";
import StyledWrapper from "./styled";
import Search from "../../common/component/Search";
@@ -18,13 +17,16 @@ import ContactsModal from "../../common/component/ContactsModal";
import ChannelModal from "../../common/component/ChannelModal";
export default function ChatPage() {
const UserMsgData = useSelector((store) => {
return store.userMsg;
const { channels, UserMsgData, ChannelMsgData } = useSelector((store) => {
return {
channels: store.channels,
UserMsgData: store.userMsg,
ChannelMsgData: store.channelMsg,
};
});
const [channelModalVisible, setChannelModalVisible] = useState(false);
const [contactsModalVisible, setContactsModalVisible] = useState(false);
const { channel_id, user_id } = useParams();
const { data: channels } = useGetChannelsQuery();
const { data: contacts } = useGetContactsQuery();
const toggleContactsModalVisible = () => {
setContactsModalVisible((prev) => !prev);
@@ -32,10 +34,16 @@ export default function ChatPage() {
const toggleChannelModalVisible = () => {
setChannelModalVisible((prev) => !prev);
};
console.log("channels", channels);
if (!channels || !contacts) return null;
const getUnreadCount = (gid) => {
return Object.values(ChannelMsgData[gid] || {}).filter((m) => m.unread)
.length;
};
if (!contacts) return null;
const Sessions = Object.keys(UserMsgData);
const tmpSessionUser = contacts.find((c) => c.uid == user_id);
const transformedChannels = Object.entries(channels).map(([key, obj]) => {
return { id: key, ...obj };
});
return (
<>
{channelModalVisible && (
@@ -60,22 +68,25 @@ export default function ChatPage() {
/>
</h3>
<nav className="nav">
{channels.map(({ gid, is_public, name, description }) => {
return (
<NavLink
title={description}
key={gid}
className="link"
to={`/chat/channel/${gid}`}
>
<span className="txt">
<ChannelIcon personal={!is_public} />
{name}
</span>
<i className="badge">12</i>
</NavLink>
);
})}
{transformedChannels.map(
({ id, is_public, name, description }) => {
let unreads = getUnreadCount(id);
return (
<NavLink
title={description}
key={id}
className="link"
to={`/chat/channel/${id}`}
>
<span className="txt">
<ChannelIcon personal={!is_public} />
{name}
</span>
{unreads > 0 && <i className="badge">{unreads}</i>}
</NavLink>
);
}
)}
</nav>
</div>
<div className="list dms">
@@ -94,6 +105,9 @@ export default function ChatPage() {
{Sessions.map((uid) => {
let currUser = contacts.find((c) => c.uid == uid);
let latestMid = Object.keys(UserMsgData[uid]).sort().pop();
let unreads = Object.values(UserMsgData[uid] || {}).filter(
(m) => m.unread
).length;
let latestMsg = UserMsgData[uid][latestMid];
return (
<NavLink key={uid} className="session" to={`/chat/dm/${uid}`}>
@@ -108,7 +122,7 @@ export default function ChatPage() {
<div className="down">
<div className="msg">{latestMsg.content}</div>
<i className="badge">3</i>
{unreads > 0 && <i className="badge">{unreads}</i>}
</div>
</div>
</NavLink>
@@ -129,7 +143,7 @@ export default function ChatPage() {
<div className="down">
<div className="msg"></div>
<i className="badge">3</i>
{/* <i className="badge">3</i> */}
</div>
</div>
</NavLink>
@@ -140,8 +154,9 @@ export default function ChatPage() {
<div className="right">
{channel_id && (
<ChannelChat
unreads={getUnreadCount(channel_id)}
cid={channel_id}
data={channels.find(({ gid }) => gid == channel_id)}
data={channels[channel_id]}
/>
)}
{user_id && <DMChat uid={user_id} />}
+143 -140
View File
@@ -1,153 +1,156 @@
import styled from 'styled-components';
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
height: 100%;
> .left {
display: flex;
flex-direction: column;
width: 260px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.1);
.list {
margin: 12px 8px;
.title {
padding: 0 8px;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
cursor: pointer;
> .txt {
user-select: none;
display: flex;
align-items: center;
gap: 5px;
font-weight: bold;
font-size: 12px;
line-height: 20px;
color: #78787c;
}
}
> .nav {
height: 100%;
> .left {
display: flex;
flex-direction: column;
gap: 4px;
a {
text-decoration: none;
}
.link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
border-radius: 4px;
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
> .txt {
display: flex;
align-items: center;
gap: 5px;
color: #1c1c1e;
font-weight: 600;
font-size: 14px;
line-height: 20px;
}
> .badge {
padding: 3px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 50%;
background: #bfbfbf;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.dot {
width: 6px;
height: 6px;
padding: 0;
}
}
}
.session {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
/* img{
width: 100%;
} */
}
.details {
display: flex;
flex-direction: column;
width: 100%;
.up {
display: flex;
justify-content: space-between;
.name {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #52525b;
}
time {
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: #78787c;
}
}
.down {
display: flex;
justify-content: space-between;
.msg {
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #78787c;
}
> .badge {
letter-spacing: -1px;
padding: 2px;
color: #fff;
height: 16px;
min-width: 16px;
width: 260px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.1);
.list {
margin: 12px 8px;
.title {
padding: 0 8px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: #1fe1f9;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.mute {
background: #bfbfbf;
justify-content: space-between;
margin-bottom: 4px;
cursor: pointer;
> .txt {
user-select: none;
display: flex;
align-items: center;
gap: 5px;
font-weight: bold;
font-size: 12px;
line-height: 20px;
color: #78787c;
}
}
> .nav {
display: flex;
flex-direction: column;
gap: 4px;
a {
text-decoration: none;
}
.link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
border-radius: 4px;
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
> .txt {
display: flex;
align-items: center;
gap: 5px;
color: #1c1c1e;
font-weight: 600;
font-size: 14px;
line-height: 20px;
}
> .badge {
color: #fff;
display: flex;
align-items: center;
justify-content: center;
height: 20px;
min-width: 20px;
border-radius: 50%;
background: #bfbfbf;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.dot {
width: 6px;
height: 6px;
padding: 0;
}
}
}
.session {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
/* img{
width: 100%;
} */
}
.details {
display: flex;
flex-direction: column;
width: 100%;
.up {
display: flex;
justify-content: space-between;
.name {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #52525b;
}
time {
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: #78787c;
}
}
.down {
display: flex;
justify-content: space-between;
.msg {
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #78787c;
white-space: nowrap;
overflow: hidden;
width: 140px;
text-overflow: ellipsis;
}
> .badge {
/* letter-spacing: -1px; */
/* padding: 2px; */
color: #fff;
height: 20px;
min-width: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: #1fe1f9;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.mute {
background: #bfbfbf;
}
}
}
}
}
}
}
}
}
}
}
}
> .right {
width: 100%;
}
> .right {
width: 100%;
}
`;
export default StyledWrapper;