feat: lots of updates

This commit is contained in:
zerosoul
2022-02-10 23:35:25 +08:00
parent e8b6c2b0f1
commit d05c5ff8f2
33 changed files with 1072 additions and 331 deletions
+25 -4
View File
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import dayjs from "dayjs";
@@ -20,7 +20,14 @@ import {
StyledHeader,
} from "./styled";
export default function ChannelChat({ cid = "", unreads = 0, data = {} }) {
export default function ChannelChat({
cid = "",
unreads = 0,
data = {},
dropFiles = [],
}) {
// const containerRef = useRef(null);
const [dragFiles, setDragFiles] = useState([]);
const dispatch = useDispatch();
const msgs = useSelector((store) => {
return store.channelMsg[cid] || {};
@@ -29,6 +36,11 @@ export default function ChannelChat({ cid = "", unreads = 0, data = {} }) {
const handleClearUnreads = () => {
dispatch(clearChannelMsgUnread(cid));
};
useEffect(() => {
if (dropFiles.length) {
setDragFiles(dropFiles);
}
}, [dropFiles]);
useEffect(() => {
console.log({ cid });
return () => {
@@ -45,6 +57,8 @@ export default function ChannelChat({ cid = "", unreads = 0, data = {} }) {
console.log("channel message list", msgs);
return (
<Layout
setDragFiles={setDragFiles}
// ref={containerRef}
header={
<StyledHeader>
<div className="txt">
@@ -73,9 +87,16 @@ export default function ChannelChat({ cid = "", unreads = 0, data = {} }) {
<div className="chat">
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
const { from_uid, content, created_at, unread } = msg;
const {
from_uid,
content,
content_type,
created_at,
unread,
} = msg;
return (
<Message
content_type={content_type}
unread={unread}
gid={cid}
mid={mid}
@@ -89,7 +110,7 @@ export default function ChannelChat({ cid = "", unreads = 0, data = {} }) {
</div>
</div>
<Send id={cid} type="channel" name={name} />
<Send dragFiles={dragFiles} id={cid} type="channel" name={name} />
<div className="placeholder"></div>
</StyledChannelChat>
{unreads != 0 && (
+52
View File
@@ -0,0 +1,52 @@
// import React from 'react'
import { NavLink, useNavigate } from "react-router-dom";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import ChannelIcon from "../../common/component/ChannelIcon";
const NavItem = ({ data, setFiles }) => {
const navigate = useNavigate();
const [{ isActive }, drop] = useDrop(() => ({
accept: [NativeTypes.FILE],
drop({ dataTransfer }) {
if (dataTransfer.files.length) {
// console.log(files, rest);
setFiles([...dataTransfer.files]);
navigate(`/chat/channel/${data.id}`);
// 重置
setTimeout(() => {
setFiles([]);
}, 300);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver(),
}),
}));
const { id, is_public, name, description, unreads } = data;
return (
<NavLink
ref={drop}
title={description}
key={id}
className={`link ${isActive ? "drop_over" : ""}`}
to={`/chat/channel/${id}`}
>
<span className="txt">
<ChannelIcon personal={!is_public} />
{name}
</span>
{unreads > 0 && <i className="badge">{unreads}</i>}
</NavLink>
);
};
export default function ChannelList({ channels, setDropFiles }) {
return channels.map(({ id, is_public, name, description, unreads }) => {
return (
<NavItem
key={id}
data={{ id, is_public, name, description, unreads }}
setFiles={setDropFiles}
/>
);
});
}
+18 -3
View File
@@ -8,7 +8,9 @@ import Layout from "../Layout";
import { StyledHeader, StyledDMChat } from "./styled";
export default function DMChat({ uid = "" }) {
export default function DMChat({ uid = "", dropFiles = [] }) {
console.log("dm files", dropFiles);
const [dragFiles, setDragFiles] = useState([]);
const msgs = useSelector((store) => {
return store.userMsg[uid] || {};
});
@@ -20,10 +22,17 @@ export default function DMChat({ uid = "" }) {
setCurrUser(contacts.find((c) => c.uid == uid));
}
}, [uid, contacts]);
useEffect(() => {
if (dropFiles.length) {
setDragFiles(dropFiles);
}
}, [dropFiles]);
if (!currUser) return null;
console.log("user msgs", msgs);
return (
<Layout
setDragFiles={setDragFiles}
header={
<StyledHeader>
<Contact interactive={false} uid={currUser.uid} />
@@ -35,9 +44,10 @@ export default function DMChat({ uid = "" }) {
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
console.log("user msg", msg);
const { from_uid, content, created_at, unread } = msg;
const { from_uid, content, content_type, created_at, unread } = msg;
return (
<Message
content_type={content_type}
unread={unread}
fromUid={from_uid}
mid={mid}
@@ -51,7 +61,12 @@ export default function DMChat({ uid = "" }) {
</div>
</StyledDMChat>
<div className="placeholder"></div>
<Send type="user" name={currUser?.name} id={currUser?.uid} />
<Send
dragFiles={dragFiles}
type="user"
name={currUser?.name}
id={currUser?.uid}
/>
</Layout>
);
}
+60
View File
@@ -0,0 +1,60 @@
// import React from "react";
import { NavLink, useNavigate } from "react-router-dom";
import dayjs from "dayjs";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import Avatar from "../../common/component/Avatar";
const NavItem = ({ data, setFiles }) => {
const navigate = useNavigate();
const [{ isActive }, drop] = useDrop(() => ({
accept: [NativeTypes.FILE],
drop({ dataTransfer }) {
if (dataTransfer.files.length) {
// console.log(files, rest);
setFiles([...dataTransfer.files]);
navigate(`/chat/dm/${data.uid}`);
// 重置
setTimeout(() => {
setFiles([]);
}, 300);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver(),
}),
}));
const { uid, user, lastMsg, unreads } = data;
return (
<NavLink
ref={drop}
key={uid}
className={`session ${isActive ? "drop_over" : ""}`}
to={`/chat/dm/${uid}`}
>
<Avatar className="avatar" url={user.avatar} id={uid} />
<div className="details">
<div className="up">
<span className="name">{user.name}</span>
<time>{dayjs(lastMsg.created_at).format("YYYY-MM-DD")}</time>
</div>
<div className="down">
<div className="msg">{lastMsg.content}</div>
{unreads > 0 && <i className="badge">{unreads}</i>}
</div>
</div>
</NavLink>
);
};
export default function DMList({ sessions, setDropFiles }) {
return sessions.map(({ uid, user, lastMsg, unreads } = {}) => {
if (!user) return null;
return (
<NavItem
key={uid}
data={{ uid, user, lastMsg, unreads }}
setFiles={setDropFiles}
/>
);
});
}
+82 -4
View File
@@ -1,11 +1,13 @@
import React from "react";
// import { useState } from "react";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import styled from "styled-components";
const StyledWrapper = styled.article`
position: relative;
width: 100%;
background: #fff;
height: 100vh;
.head {
> .head {
height: 56px;
padding: 0 20px;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
@@ -21,16 +23,92 @@ const StyledWrapper = styled.article`
border-top: 1px solid transparent;
}
}
.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 function Layout({ children, header, contacts = null }) {
export default function Layout({
children,
header,
contacts = null,
setDragFiles,
}) {
const [{ isActive }, drop] = useDrop(() => ({
accept: [NativeTypes.FILE],
drop({ files }) {
if (files.length) {
setDragFiles([...files]);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver(),
}),
}));
return (
<StyledWrapper>
<StyledWrapper className="animate__animated animate__fadeIn" ref={drop}>
<header className="head">{header}</header>
<main className="main">
{children}
{contacts && <div className="members">{contacts}</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>
</StyledWrapper>
);
}
+27 -51
View File
@@ -2,7 +2,7 @@
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";
@@ -10,13 +10,16 @@ import { useGetContactsQuery } from "../../app/services/contact";
import StyledWrapper from "./styled";
import Search from "../../common/component/Search";
import Avatar from "../../common/component/Avatar";
import ChannelIcon from "../../common/component/ChannelIcon";
import ChannelChat from "./ChannelChat";
import DMChat from "./DMChat";
import ChannelList from "./ChannelList";
import ContactsModal from "../../common/component/ContactsModal";
import ChannelModal from "../../common/component/ChannelModal";
import DMList from "./DMList";
export default function ChatPage() {
const [channelDropFiles, setChannelDropFiles] = useState([]);
const [userDropFiles, setUserDropFiles] = useState([]);
const { channels, UserMsgData, ChannelMsgData } = useSelector((store) => {
return {
channels: store.channels,
@@ -38,11 +41,23 @@ export default function ChatPage() {
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 };
const unreads = Object.values(ChannelMsgData[key] || {}).filter(
(m) => m.unread
).length;
return { id: key, ...obj, unreads };
});
const sessions = Object.keys(UserMsgData).map((uid) => {
let currUser = contacts.find((c) => c.uid == uid);
if (!currUser) return undefined;
let lastMid = Object.keys(UserMsgData[uid]).sort().pop();
let unreads = Object.values(UserMsgData[uid] || {}).filter((m) => m.unread)
.length;
let lastMsg = UserMsgData[uid][lastMid];
return { user: currUser, unreads, uid, lastMsg };
});
return (
<>
@@ -68,25 +83,10 @@ export default function ChatPage() {
/>
</h3>
<nav className="nav">
{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>
);
}
)}
<ChannelList
channels={transformedChannels}
setDropFiles={setChannelDropFiles}
/>
</nav>
</div>
<div className="list dms">
@@ -102,33 +102,8 @@ export default function ChatPage() {
/>
</h3>
<nav className="nav">
{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}`}>
<Avatar className="avatar" url={currUser.avatar} id={uid} />
<div className="details">
<div className="up">
<span className="name">{currUser.name}</span>
<time>
{dayjs(latestMsg.created_at).format("YYYY-MM-DD")}
</time>
</div>
<div className="down">
<div className="msg">{latestMsg.content}</div>
{unreads > 0 && <i className="badge">{unreads}</i>}
</div>
</div>
</NavLink>
);
})}
{user_id && !Sessions.includes(user_id) && (
<DMList sessions={sessions} setDropFiles={setUserDropFiles} />
{user_id && !Object.keys(UserMsgData).includes(user_id) && (
<NavLink className="session" to={`/chat/dm/${user_id}`}>
<Avatar
className="avatar"
@@ -157,9 +132,10 @@ export default function ChatPage() {
unreads={getUnreadCount(channel_id)}
cid={channel_id}
data={channels[channel_id]}
dropFiles={channelDropFiles}
/>
)}
{user_id && <DMChat uid={user_id} />}
{user_id && <DMChat uid={user_id} dropFiles={userDropFiles} />}
</div>
</StyledWrapper>
</>
+5
View File
@@ -83,6 +83,7 @@ const StyledWrapper = styled.div`
&.active {
background: rgba(116, 127, 141, 0.1);
}
.avatar {
width: 32px;
height: 32px;
@@ -145,6 +146,10 @@ const StyledWrapper = styled.div`
}
}
}
/* drop files effect */
.drop_over {
outline: 2px solid #52edff;
}
}
}
}
-1
View File
@@ -6,7 +6,6 @@ import { useSelector, useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { clearAuthData } from "../../app/slices/auth.data";
import { useLazyLogoutQuery } from "../../app/services/auth";
const StyledWrapper = styled.div`
+23 -6
View File
@@ -1,8 +1,9 @@
// import React from 'react';
// import { useState } from "react";
// import { useEffect } from "react";
import { Outlet, NavLink } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { toggleMenuExpand } from "../../app/slices/ui";
// import { setAuthData } from "../../app/slices/auth.data";
import StyledWrapper from "./styled";
import ServerDropList from "./ServerDropList";
import Tools from "./Tools";
@@ -16,21 +17,37 @@ import NotificationHub from "../../common/component/NotificationHub";
export default function HomePage() {
const dispatch = useDispatch();
const { menuExpand, token, usersVersion } = useSelector((store) => {
const {
menuExpand,
authData: { token, usersVersion, afterMid },
} = useSelector((store) => {
return {
token: store.authData.token,
usersVersion: store.authData.usersVersion,
authData: store.authData,
menuExpand: store.ui.menuExpand,
};
});
const { data, error, success } = usePreload();
const { data, loading, error, success } = usePreload();
// useEffect(() => {
// if (authData) {
// dispatch(setAuthData(data));
// }
// }, [authData]);
const toggleExpand = () => {
dispatch(toggleMenuExpand());
};
console.log({ data, error, success });
if (loading) {
return "loading";
}
return (
<>
<NotificationHub token={token} usersVersion={usersVersion} />
<NotificationHub
token={token}
usersVersion={usersVersion}
afterMid={afterMid}
/>
<StyledWrapper>
<div className={`col left ${menuExpand ? "expand" : ""}`}>
<ServerDropList
+22 -2
View File
@@ -1,5 +1,9 @@
// import React from 'react';
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { useGetContactsQuery } from "../../app/services/contact";
import { clearAuthData } from "../../app/slices/auth.data";
// import { useGetChannelsQuery } from "../../app/services/channel";
import { useGetServerQuery } from "../../app/services/server";
// pollingInterval: 0,
@@ -7,6 +11,12 @@ const querySetting = {
refetchOnMountOrArgChange: true,
};
export default function usePreload() {
const [checked, setChecked] = useState(false);
const loginedUser = useSelector((store) => {
return store.authData.user;
});
const dispatch = useDispatch();
const navigate = useNavigate();
const {
isLoading: contactsLoading,
isSuccess: contactsSuccess,
@@ -25,9 +35,19 @@ export default function usePreload() {
// isError: groupsError,
// data: groups,
// } = useGetChannelsQuery(undefined, querySetting);
useEffect(() => {
if (contacts) {
const matchedUser = contacts.find((c) => c.uid == loginedUser.uid);
if (!matchedUser) {
dispatch(clearAuthData());
navigate("/login");
}
setChecked(true);
}
}, [contacts]);
return {
loading: contactsLoading && serverLoading,
loading: contactsLoading || serverLoading || !checked,
error: contactsError && serverError,
success: contactsSuccess && serverSuccess,
data: {
+70
View File
@@ -0,0 +1,70 @@
// import { useState } from "react";
import { Route, Routes, HashRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { persistStore } from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
// import Welcome from './Welcome'
import NotFoundPage from "./404";
import LoginPage from "./login";
import HomePage from "./home";
import ChatPage from "./chat";
import ContactsPage from "./contacts";
import RequireAuth from "../common/component/RequireAuth";
import store from "../app/store.with.persist";
// import getStore from "../app/store";
const PageRoutes = () => {
return (
<HashRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/"
element={
<RequireAuth>
<HomePage />
</RequireAuth>
}
>
<Route index element={<ChatPage />} />
<Route path="chat">
<Route index element={<ChatPage />} />
<Route path="channel/:channel_id" element={<ChatPage />} />
<Route path="dm/:user_id" element={<ChatPage />} />
</Route>
<Route path="contacts">
<Route index element={<ContactsPage />} />
<Route path=":user_id" element={<ContactsPage />} />
</Route>
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</HashRouter>
);
};
// const local_key = "AUTH_DATA";
export default function ReduxRoutes() {
// const [authData, setAuthData] = useState(
// JSON.parse(localStorage.getItem(local_key))
// );
// const updateAuthData = (data) => {
// localStorage.setItem(local_key, JSON.stringify(data));
// setAuthData(data);
// };
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistStore(store)}>
<PageRoutes />
</PersistGate>
{/* {authData ? (
<PersistGate loading={null} persistor={persistStore(getPersistStore())}>
<PageRoutes authData={authData} updateAuthData={updateAuthData} />
</PersistGate>
) : (
<PageRoutes authData={authData} updateAuthData={updateAuthData} />
)} */}
</Provider>
);
}
+5 -3
View File
@@ -8,6 +8,7 @@ import { useLoginMutation } from "../../app/services/auth";
import { setAuthData } from "../../app/slices/auth.data";
export default function LoginPage() {
// const { token } = useSelector((store) => store.authData);
const navigateTo = useNavigate();
const dispatch = useDispatch();
const [login] = useLoginMutation();
@@ -21,8 +22,7 @@ export default function LoginPage() {
console.log("wtf", input);
const { data, error } = await login({
...input,
device: "web",
device_token: "test",
type: "password",
});
if (error) {
console.log(error);
@@ -37,9 +37,11 @@ export default function LoginPage() {
return;
}
if (data) {
dispatch(setAuthData(data));
// 更新本地认证信息
toast.success("login success");
dispatch(setAuthData(data));
navigateTo("/");
// location.reload(true);
}
};
const handleInput = (evt) => {