diff --git a/src/assets/icons/lock.svg b/src/assets/icons/lock.svg new file mode 100644 index 00000000..099c5910 --- /dev/null +++ b/src/assets/icons/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/routes/chat/SessionList/ContextMenu.js b/src/routes/chat/SessionList/ContextMenu.js new file mode 100644 index 00000000..881d8b5d --- /dev/null +++ b/src/routes/chat/SessionList/ContextMenu.js @@ -0,0 +1,103 @@ +// import React from 'react' +import Tippy from "@tippyjs/react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate, useLocation, useMatch } from "react-router-dom"; +import { useUpdateMuteSettingMutation } from "../../../app/services/contact"; +import { useReadMessageMutation } from "../../../app/services/message"; +import { removeUserSession } from "../../../app/slices/message.user"; +import ContextMenu from "../../../common/component/ContextMenu"; + +export default function SessionContextMenu({ + context, + id, + visible, + mid, + hide, + deleteChannel, + setInviteChannelId, + children +}) { + const [muteChannel] = useUpdateMuteSettingMutation(); + const [updateReadIndex] = useReadMessageMutation(); + const pathMatched = useMatch(`/chat/dm/${id}`); + const dispatch = useDispatch(); + const navigateTo = useNavigate(); + const { pathname } = useLocation(); + const { channelMuted } = useSelector((store) => { + return { + channelMuted: context == "channel" ? store.footprint.muteChannels[id] : false + }; + }); + const handleChannelSetting = () => { + navigateTo(`/setting/channel/${id}?f=${pathname}`); + }; + const handleReadAll = () => { + // console.log("last mid", mids, lastMid); + if (mid) { + const param = + context == "dm" ? { users: [{ uid: +id, mid }] } : { groups: [{ gid: +id, mid }] }; + updateReadIndex(param); + } + }; + const handleRemoveSession = () => { + dispatch(removeUserSession(id)); + if (pathMatched) { + navigateTo("/chat"); + } + }; + const handleChannelMute = () => { + const data = channelMuted ? { remove_groups: [id] } : { add_groups: [{ gid: id }] }; + muteChannel(data); + }; + const items = + context == "dm" + ? [ + { + title: "Mark As Read", + handler: handleReadAll + }, + { + title: "Hide Session", + danger: true, + handler: handleRemoveSession + } + ] + : [ + { + title: "Settings", + underline: true, + handler: handleChannelSetting + }, + { + title: "Mark As Read", + // underline: true + handler: handleReadAll + }, + { + title: channelMuted ? "Unmute" : "Mute", + handler: handleChannelMute + }, + { + title: "Invite People", + handler: setInviteChannelId.bind(null, id) + }, + { + title: "Delete Channel", + danger: true, + handler: deleteChannel.bind(null, id) + } + ]; + return ( + } + > + {children} + + ); +} diff --git a/src/routes/chat/SessionList/Session.js b/src/routes/chat/SessionList/Session.js new file mode 100644 index 00000000..580ff99e --- /dev/null +++ b/src/routes/chat/SessionList/Session.js @@ -0,0 +1,108 @@ +import { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import dayjs from "dayjs"; +import { useDrop } from "react-dnd"; +import { NativeTypes } from "react-dnd-html5-backend"; +import relativeTime from "dayjs/plugin/relativeTime"; +import ContextMenu from "./ContextMenu"; +import { renderPreviewMessage } from "../utils"; +import Contact from "../../../common/component/Contact"; +import iconChannel from "../../../assets/icons/channel.svg?url"; +import IconLock from "../../../assets/icons/lock.svg"; +import useContextMenu from "../../../common/hook/useContextMenu"; +import { useNavigate, NavLink } from "react-router-dom"; +dayjs.extend(relativeTime); +export default function Session({ + type = "dm", + id, + mid, + setFiles, + setDeleteChannelId, + setInviteChannelId +}) { + const navigate = useNavigate(); + const [{ isActive }, drop] = useDrop(() => ({ + accept: [NativeTypes.FILE], + drop({ dataTransfer }) { + if (dataTransfer.files.length) { + // console.log(files, rest); + setFiles([...dataTransfer.files]); + navigate(type == "dm" ? `/chat/dm/${id}` : `/chat/channel/${id}`); + // 重置 + setTimeout(() => { + setFiles([]); + }, 300); + } + }, + collect: (monitor) => ({ + isActive: monitor.canDrop() && monitor.isOver() + }) + })); + const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu(); + const [data, setData] = useState(null); + const { messageData, contactData, channelData } = useSelector((store) => { + return { + messageData: store.message, + contactData: store.contacts.byId, + channelData: store.channels.byId + }; + }); + const handleImageError = (evt) => { + evt.target.classList.add("channel_default"); + evt.target.src = iconChannel; + }; + useEffect(() => { + const tmp = type == "dm" ? contactData[id] : channelData[id]; + if (!tmp) return; + const { name, icon, avatar, is_public = true } = tmp; + const session = + type == "dm" ? { name, icon: avatar, mid, is_public } : { name, icon, mid, is_public }; + setData(session); + }, [id, mid, type, contactData, channelData]); + if (!data) return null; + const previewMsg = messageData[mid] || {}; + const { name, icon, is_public } = data; + return ( +
  • + + +
    + {type == "dm" ? ( + + ) : ( + + )} +
    +
    +
    + + {name} {!is_public && } + + {dayjs(previewMsg.created_at).fromNow()} +
    +
    + {renderPreviewMessage(previewMsg)} +
    +
    +
    +
    +
  • + ); +} diff --git a/src/routes/chat/SessionList/index.js b/src/routes/chat/SessionList/index.js new file mode 100644 index 00000000..ea1aac49 --- /dev/null +++ b/src/routes/chat/SessionList/index.js @@ -0,0 +1,84 @@ +import { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import Styled from "./styled"; +import Session from "./Session"; +import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal"; +import InviteModal from "../../../common/component/InviteModal"; +export default function SessionList() { + const [deleteId, setDeleteId] = useState(null); + const [inviteChannelId, setInviteChannelId] = useState(null); + const [sessions, setSessions] = useState([]); + const { channelIDs, DMs, readChannels, readUsers, channelMessage, userMessage, loginUid } = + useSelector((store) => { + return { + loginUid: store.authData.uid, + channelIDs: store.channels.ids, + DMs: store.userMessage.ids, + userMessage: store.userMessage.byId, + channelMessage: store.channelMessage, + readChannels: store.footprint.readChannels, + readUsers: store.footprint.readUsers + }; + }); + useEffect(() => { + const cSessions = channelIDs.map((id) => { + const mids = channelMessage[id]; + if (!mids || mids.length == 0) { + return { mid: null, unreads: 0, id, type: "channel" }; + } + const mid = [...mids].pop(); + return { key: `channel_${id}`, id, mid, type: "channel" }; + }); + const uSessions = DMs.map((id) => { + const mids = userMessage[id]; + if (!mids || mids.length == 0) { + return { mid: null, unreads: 0, id, type: "dm" }; + } + const mid = [...mids].pop(); + return { key: `dm_${id}`, type: "dm", id, mid }; + }); + + setSessions( + [...cSessions, ...uSessions].sort((a, b) => { + return b.mid - a.mid; + }) + ); + }, [channelIDs, DMs, channelMessage, readChannels, readUsers, loginUid, userMessage]); + return ( + <> + + {sessions.map((s) => { + const { key, type, id, mid } = s; + return ( + + ); + })} + + {deleteId && ( + { + setDeleteId(null); + }} + /> + )} + {inviteChannelId && ( + { + setInviteChannelId(null); + }} + /> + )} + + ); +} diff --git a/src/routes/chat/SessionList/styled.js b/src/routes/chat/SessionList/styled.js new file mode 100644 index 00000000..886dbb41 --- /dev/null +++ b/src/routes/chat/SessionList/styled.js @@ -0,0 +1,78 @@ +import styled from "styled-components"; +const Styled = styled.ul` + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px; + height: calc(100vh - 56px - 56px - 16px); + overflow: auto; + > .session { + > a { + display: flex; + gap: 8px; + border-radius: 8px; + padding: 8px; + width: 100%; + &.active, + &:hover { + background: rgba(116, 127, 141, 0.2); + } + .icon { + flex: 1; + background-color: #eee; + border-radius: 50%; + img { + width: 40px; + height: 40px; + &.channel_default { + padding: 5px; + /* height: 35px; */ + } + } + } + .details { + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + .up { + display: flex; + align-items: center; + justify-content: space-between; + .name { + font-weight: 600; + font-size: 14px; + line-height: 20px; + color: #52525b; + max-width: 112px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .time { + font-weight: 500; + font-size: 12px; + line-height: 18px; + color: #78787c; + } + } + .down { + display: flex; + align-items: center; + .msg { + font-weight: 400; + font-size: 12px; + line-height: 18px; + color: #78787c; + white-space: nowrap; + overflow: hidden; + width: 140px; + text-overflow: ellipsis; + } + } + } + } + } +`; + +export default Styled; diff --git a/src/routes/chat/index.js b/src/routes/chat/index.js index 1eac1a96..71dc0e60 100644 --- a/src/routes/chat/index.js +++ b/src/routes/chat/index.js @@ -3,22 +3,17 @@ import { useState } from "react"; import { useParams } from "react-router-dom"; import { useSelector } from "react-redux"; -import { AiOutlineCaretDown } from "react-icons/ai"; - import StyledWrapper from "./styled"; -import AddIcon from "../../assets/icons/add.svg"; import BlankPlaceholder from "../../common/component/BlankPlaceholder"; import Server from "../../common/component/Server"; -import Tooltip from "../../common/component/Tooltip"; +// import Tooltip from "../../common/component/Tooltip"; // import Contact from "../../common/component/Contact"; import CurrentUser from "../../common/component/CurrentUser"; 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"; - +import SessionList from "./SessionList"; export default function ChatPage() { const [channelDropFiles, setChannelDropFiles] = useState([]); const [userDropFiles, setUserDropFiles] = useState([]); @@ -36,11 +31,6 @@ export default function ChatPage() { const toggleChannelModalVisible = () => { setChannelModalVisible((prev) => !prev); }; - const handleToggleExpand = (evt) => { - const { currentTarget } = evt; - const listEle = currentTarget.parentElement.parentElement; - listEle.classList.toggle("collapse"); - }; const tmpUid = sessionUids.findIndex((i) => i == user_id) > -1 ? null : user_id; // console.log("temp uid", tmpUid); const placeholderVisible = !channel_id && !user_id; @@ -53,37 +43,7 @@ export default function ChatPage() {
    -
    -

    - - - CHANNELS - - - - -

    - -
    -
    -

    - - - DIRECT MESSAGE - - - - -

    - -
    +
    diff --git a/src/routes/chat/styled.js b/src/routes/chat/styled.js index 86d682b2..3d46300a 100644 --- a/src/routes/chat/styled.js +++ b/src/routes/chat/styled.js @@ -107,8 +107,6 @@ const StyledWrapper = styled.div` text-overflow: ellipsis; } > .badge { - /* letter-spacing: -1px; */ - /* padding: 2px; */ color: #fff; height: 20px; min-width: 20px; diff --git a/src/routes/chat/useSession.js b/src/routes/chat/useSession.js new file mode 100644 index 00000000..1ad6dd01 --- /dev/null +++ b/src/routes/chat/useSession.js @@ -0,0 +1,25 @@ +import { useState } from "react"; +import { useSelector } from "react-redux"; +// import { useUpdateChannelMutation } from "../../app/services/channel"; +import { useUpdateMuteSettingMutation } from "../../app/services/contact"; +import { useReadMessageMutation } from "../../app/services/message"; + +export default function useSession({ type, id }) { + const [inviteModalVisible, setInviteModalVisible] = useState(false); + const [muteChannel] = useUpdateMuteSettingMutation(); + const [updateReadIndex] = useReadMessageMutation(); + const { messageData, contactData, channelData } = useSelector((store) => { + return { + messageData: store.message, + contactData: store.contacts.byId, + channelData: store.channels.byId + }; + }); + const toggleInviteModal = () => { + setInviteModalVisible((prev) => !prev); + }; + return { + inviteModalVisible, + toggleInviteModal + }; +}