refactor: finished tailwind

This commit is contained in:
Tristan Yang
2023-02-08 15:03:30 +08:00
parent cb08a443f3
commit ffd68f2c06
55 changed files with 260 additions and 1313 deletions
+8 -17
View File
@@ -1,19 +1,10 @@
import { FC, FormEvent } from "react";
import styled from "styled-components";
import usePinMessage from "../../../common/hook/usePinMessage";
import IconSurprise from "../../../assets/icons/emoji.surprise.svg";
import IconClose from "../../../assets/icons/close.svg";
import { useTranslation } from "react-i18next";
import PinnedMessage from "../../../common/component/PinnedMessage";
const Styled = styled.div`
.preview {
background: none;
.down img {
width: 100% !important;
height: auto !important;
}
}
`;
type Props = {
id: number;
};
@@ -27,23 +18,23 @@ const PinList: FC<Props> = ({ id }: Props) => {
};
const noPins = pins.length == 0;
return (
<Styled className="p-4 drop-shadow-md overflow-y-scroll max-h-[90vh] min-w-[486px] rounded-xl bg-gray-50">
<h4 className=" text-gray-600 mb-4 font-semibold">{t("pinned_msg")}({pins.length})</h4>
<div className="p-4 drop-shadow-md overflow-y-scroll max-h-[90vh] min-w-[486px] rounded-xl bg-gray-50 dark:bg-gray-800">
<h4 className=" text-gray-600 dark:text-gray-400 mb-4 font-semibold">{t("pinned_msg")}({pins.length})</h4>
{noPins ? (
<div className="flex flex-col items-center gap-2 w-full p-4">
<IconSurprise />
<div className="w-60 font-semibold text-gray-500 text-center">{t("pin_empty_tip")}</div>
<div className="w-60 font-semibold text-gray-500 dark:text-gray-300 text-center">{t("pin_empty_tip")}</div>
</div>
) : (
<ul className="flex flex-col gap-2">
{pins.map((data) => {
return (
<li key={data.mid} className="group relative border border-solid border-slate-100 rounded-md ">
<li key={data.mid} className="group relative border border-solid border-slate-100 dark:border-slate-600 rounded-md ">
<PinnedMessage data={data} />
<div className="invisible group-hover:visible flex items-center gap-1 absolute top-1 right-1 p-1 border border-solid border-black/10 rounded-md">
<div className="invisible group-hover:visible flex items-center gap-1 absolute top-1 right-1 p-1 border border-solid border-black/10 dark:border-gray-500 rounded-md">
{canPin && (
<button className="flex bg-none border-none" data-mid={data.mid} onClick={handleUnpin}>
<IconClose className="fill-slate-900" />
<IconClose className="fill-slate-900 dark:fill-slate-300" />
</button>
)}
</div>
@@ -52,7 +43,7 @@ const PinList: FC<Props> = ({ id }: Props) => {
})}
</ul>
)}
</Styled>
</div>
);
};
export default PinList;
+12 -21
View File
@@ -8,7 +8,6 @@ import FavList from "../FavList";
import { useReadMessageMutation } from "../../../app/services/message";
import { updateRememberedNavs } from "../../../app/slices/ui";
import useMessageFeed from "../../../common/hook/useMessageFeed";
import useConfig from "../../../common/hook/useConfig";
import ChannelIcon from "../../../common/component/ChannelIcon";
import Tooltip from "../../../common/component/Tooltip";
import User from "../../../common/component/User";
@@ -18,13 +17,11 @@ import EditIcon from "../../../assets/icons/edit.svg";
import IconFav from "../../../assets/icons/bookmark.svg";
import IconPeople from "../../../assets/icons/people.svg";
import IconPin from "../../../assets/icons/pin.svg";
import IconHeadphone from "../../../assets/icons/headphone.svg";
import addIcon from "../../../assets/icons/add.svg?url";
import IconAdd from "../../../assets/icons/add.svg";
import InviteModal from "../../../common/component/InviteModal";
import LoadMore from "../LoadMore";
import { useAppSelector } from "../../../app/store";
import { AgoraConfig } from "../../../types/server";
import { useTranslation } from "react-i18next";
type Props = {
cid?: number;
@@ -32,7 +29,6 @@ type Props = {
};
function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
const { t } = useTranslation("chat");
const { values: agoraConfig } = useConfig("agora");
const {
pulling,
list: msgIds,
@@ -86,6 +82,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
const readIndex = footprint.readChannels[cid];
const pinCount = data?.pinned_messages?.length || 0;
const feeds = [...msgIds, ...appends];
const toolClass = `relative cursor-pointer`;
return (
<>
{addMemberModalVisible && <InviteModal cid={cid} closeModal={toggleAddVisible} />}
@@ -94,14 +91,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
context="channel"
dropFiles={dropFiles}
aside={
<ul className="tools">
{(agoraConfig as AgoraConfig)?.enabled && (
<li className="tool">
<Tooltip tip="Voice/Video Chat" placement="left">
<IconHeadphone />
</Tooltip>
</li>
)}
<ul className="flex flex-col gap-6">
<Tooltip tip={t("pin")} placement="left">
<Tippy
placement="left-start"
@@ -111,8 +101,9 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
trigger="click"
content={<PinList id={cid} />}
>
<li className={`tool ${pinCount > 0 ? "badge" : ""}`} data-count={pinCount}>
<IconPin />
<li className={`${toolClass}`}>
{pinCount > 0 ? <span className="absolute -top-2 -right-2 flex-center w-4 h-4 rounded-full bg-primary-400 text-white font-bold text-[10px]">{pinCount}</span> : null}
<IconPin className="fill-gray-500" />
</li>
</Tippy>
</Tooltip>
@@ -125,23 +116,23 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
trigger="click"
content={<FavList cid={cid} />}
>
<li className={`tool fav`} data-count={pinCount}>
<IconFav />
<li className={`${toolClass}`}>
<IconFav className="fill-gray-500" />
</li>
</Tippy>
</Tooltip>
<li
className={`tool ${membersVisible ? "active" : ""}`}
className={`${toolClass}`}
onClick={toggleMembersVisible}
>
<Tooltip tip={t("channel_members")} placement="left">
<IconPeople />
<IconPeople className={membersVisible ? "fill-gray-600" : ""} />
</Tooltip>
</li>
</ul>
}
header={
<header className="head flex items-center h-full justify-center md:justify-between">
<header className="box-border h-14 px-5 border-solid border-b border-b-black/10 flex items-center justify-center md:justify-between">
<div className="flex items-center gap-1 text-base">
<ChannelIcon personal={!is_public} />
<span className="text-gray-800 dark:text-white">{name}</span>
@@ -153,7 +144,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
<div className={`flex-col gap-1 w-[226px] h-[calc(100vh_-_56px_-_22px)] overflow-y-scroll p-2 shadow-[inset_1px_0px_0px_rgba(0,_0,_0,_0.1)] ${membersVisible ? "flex" : "hidden"}`}>
{addVisible && (
<div className="cursor-pointer flex items-center justify-start gap-1 select-none rounded-lg p-2.5 hover:bg-gray-500/10" onClick={toggleAddVisible}>
<img className="w-6 h-6" src={addIcon} />
<IconAdd className="w-6 h-6 dark:fill-slate-300" />
<div className="font-semibold text-sm text-gray-600 dark:text-gray-50">{t("add_channel_members")}</div>
</div>
)}
-196
View File
@@ -1,196 +0,0 @@
import { useState, FC, MouseEvent } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import Tippy from "@tippyjs/react";
import useContextMenu from "../../../common/hook/useContextMenu";
import ContextMenu from "../../../common/component/ContextMenu";
import InviteModal from "../../../common/component/InviteModal";
import Tooltip from "../../../common/component/Tooltip";
import IconSetting from "../../../assets/icons/setting.svg";
import IconInvite from "../../../assets/icons/invite.from.channel.svg";
import { useReadMessageMutation } from "../../../app/services/message";
import { useUpdateMuteSettingMutation } from "../../../app/services/user";
import StyledLink from "./styled";
import ChannelIcon from "../../../common/component/ChannelIcon";
import { getUnreadCount } from "../utils";
import { useAppSelector } from "../../../app/store";
import { useTranslation } from "react-i18next";
interface IProps {
id: number;
setFiles: (files: File[]) => void;
toggleRemoveConfirm: (id: number) => void;
}
const NavItem: FC<IProps> = ({ id, setFiles, toggleRemoveConfirm }) => {
const { t } = useTranslation();
const { pathname } = useLocation();
const [inviteModalVisible, setInviteModalVisible] = useState(false);
const navigate = useNavigate();
const [muteChannel] = useUpdateMuteSettingMutation();
const [updateReadIndex] = useReadMessageMutation();
const {
visible: contextMenuVisible,
offset,
handleContextMenuEvent,
hideContextMenu
} = useContextMenu();
const {
channel,
mids = [],
messageData,
readIndex,
muted,
loginUid = 0
} = useAppSelector((store) => {
return {
channel: store.channels.byId[id],
mids: store.channelMessage[id],
messageData: store.message,
loginUid: store.authData.user?.uid,
readIndex: store.footprint.readChannels[id],
muted: store.footprint.muteChannels[id]
};
});
const handleChannelSetting = (evt: MouseEvent<SVGElement>) => {
evt.preventDefault();
evt.stopPropagation();
const { id } = evt.currentTarget.dataset;
if (id) {
navigate(`/setting/channel/${id}?f=${pathname}`);
}
};
// todo: type check
const [{ isActive }, drop] = useDrop<any, unknown, { isActive: boolean }>(() => ({
accept: [NativeTypes.FILE],
drop({ dataTransfer }) {
if (dataTransfer.files.length) {
// console.log(files, rest);
setFiles([...dataTransfer.files]);
navigate(`/chat/channel/${id}`);
// 重置
setTimeout(() => {
setFiles([]);
}, 300);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver()
})
}));
const handleReadAll = () => {
const lastMid = mids[mids.length - 1];
if (lastMid) {
const param = { groups: [{ gid: id, mid: lastMid }] };
updateReadIndex(param);
}
};
const toggleInviteModalVisible = (evt?: Event) => {
if (evt) {
evt.preventDefault();
evt.stopPropagation();
}
setInviteModalVisible((prev) => !prev);
};
const handleMute = () => {
const data = muted ? { remove_groups: [id] } : { add_groups: [{ gid: id }] };
muteChannel(data);
};
if (!channel) return null;
const { is_public, name, owner } = channel;
const { unreads = 0, mentions = [] } = getUnreadCount({
mids,
messageData,
readIndex,
loginUid
});
const isMentions = mentions.length !== 0;
const inviteIconVisible = is_public || owner == loginUid;
return (
<>
<Tippy
interactive
placement="right-start"
popperOptions={{ strategy: "fixed" }}
offset={[offset.y, offset.x]}
visible={contextMenuVisible}
onClickOutside={hideContextMenu}
key={id}
content={
<ContextMenu
hideMenu={hideContextMenu}
items={[
{
title: t("action.mark_read"),
underline: true,
handler: handleReadAll
},
{
title: muted ? t("action.unmute") : t("action.mute"),
handler: handleMute
},
{
title: t("action.invite_people"),
handler: toggleInviteModalVisible
},
{
title: t("channel.delete", { ns: "setting" }),
danger: true,
handler: toggleRemoveConfirm.bind(null, id)
}
]}
/>
}
>
<StyledLink
data-cid={id}
onContextMenu={handleContextMenuEvent}
ref={drop}
key={id}
className={`link ${isActive ? "drop_over" : ""} ${muted ? "muted" : ""}`}
activeclassname="link_active"
to={`/chat/channel/${id}`}
>
<div className={`name`} title={name}>
<ChannelIcon personal={!is_public} muted={!!muted} />
<span className={`txt ${unreads == 0 ? "read" : ""}`}>{name}</span>
</div>
<div className="icons">
{inviteIconVisible && (
<Tooltip placement="bottom" tip="Add Member">
<IconInvite
className="icon invite"
data-id={id}
onClick={toggleInviteModalVisible}
></IconInvite>
</Tooltip>
)}
<Tooltip placement="bottom" tip="Channel Setting">
<IconSetting
className="icon setting"
data-id={id}
onClick={handleChannelSetting}
></IconSetting>
</Tooltip>
{unreads > 0 && (
<i className={`badge ${isMentions ? "mention" : ""}`}>
{isMentions ? mentions.length : unreads}
</i>
)}
</div>
</StyledLink>
</Tippy>
{inviteModalVisible && (
<InviteModal
type="channel"
cid={id}
title={channel?.name}
closeModal={toggleInviteModalVisible}
/>
)}
</>
);
};
export default NavItem;
-33
View File
@@ -1,33 +0,0 @@
import { useState } from "react";
import DeleteConfirmModal from "../../settingChannel/DeleteConfirmModal";
import NavItem from "./NavItem";
import { useAppSelector } from "../../../app/store";
export default function ChannelList({ setDropFiles }: { setDropFiles: (files: File[]) => void }) {
const [currId, setCurrId] = useState<number>();
const { channelIds } = useAppSelector((store) => {
return { channelIds: store.channels.ids };
});
const setRemoveChannel = (cid?: number) => {
setCurrId(cid);
};
return (
<>
{channelIds.map((cid) => {
return (
<NavItem
key={cid}
toggleRemoveConfirm={setRemoveChannel}
id={cid}
setFiles={setDropFiles}
/>
);
})}
{typeof currId !== "undefined" && (
<DeleteConfirmModal id={currId} closeModal={setRemoveChannel} />
)}
</>
);
}
-86
View File
@@ -1,86 +0,0 @@
import styled from "styled-components";
import { NavLink } from "react-router-dom";
const Styled = styled(NavLink)`
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
border-radius: 4px;
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
> .name {
display: flex;
align-items: center;
gap: 8px;
.txt {
color: #1c1c1e;
font-weight: 600;
font-size: 14px;
line-height: 20px;
width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.read {
color: #616161;
}
}
}
> .icons {
display: flex;
align-items: center;
gap: 8px;
> .icon {
display: none;
width: 16px;
height: 16px;
&:hover path {
fill: #667085;
}
}
> .badge {
color: #fff;
display: flex;
align-items: center;
justify-content: center;
height: 20px;
min-width: 20px;
border-radius: 50%;
background: #22ccee;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.mention {
background: #f97066;
}
&.dot {
min-width: unset;
width: 6px;
height: 6px;
padding: 0;
}
}
}
&.muted {
.name .txt {
color: #d0d5dd;
}
.icons .badge {
background: #bfbfbf;
}
}
&:hover > .icons {
> .badge {
display: none;
}
> .icon {
display: flex;
}
}
`;
export default Styled;
+4 -4
View File
@@ -46,7 +46,7 @@ const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => {
context="user"
dropFiles={dropFiles}
aside={
<ul className="tools">
<ul className="flex flex-col gap-6">
<Tooltip tip="Saved Items" placement="left">
<Tippy
placement="left-start"
@@ -56,15 +56,15 @@ const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => {
trigger="click"
content={<FavList uid={uid} />}
>
<li className={`tool fav`}>
<FavIcon />
<li className={`relative cursor-pointer fav`}>
<FavIcon className="fill-gray-500" />
</li>
</Tippy>
</Tooltip>
</ul>
}
header={
<header className="head h-full flex items-center justify-between">
<header className="box-border h-14 px-5 border-b border-b-black/10 flex items-center justify-between">
<User interactive={false} uid={currUser.uid} />
</header>
}
-139
View File
@@ -1,139 +0,0 @@
// @ts-nocheck
import { useEffect, useState, FC } from "react";
import { NavLink, useNavigate, useMatch } from "react-router-dom";
import { useDrop } from "react-dnd";
import { useDispatch } from "react-redux";
import { NativeTypes } from "react-dnd-html5-backend";
import dayjs from "dayjs";
import Tippy from "@tippyjs/react";
import { useReadMessageMutation } from "../../../app/services/message";
import { removeUserSession } from "../../../app/slices/message.user";
import useNormalizeMessage from "../../../common/hook/useNormalizeMessage";
import useContextMenu from "../../../common/hook/useContextMenu";
import ContextMenu from "../../../common/component/ContextMenu";
import { renderPreviewMessage } from "../utils";
import User from "../../../common/component/User";
import { ContentTypes } from "../../../app/config";
import { useAppSelector } from "../../../app/store";
import { ArchiveMessage } from "../../../types/resource";
import { useTranslation } from "react-i18next";
interface IProps {
uid: number;
mid?: number;
unreads: number;
setFiles: (files: File[]) => void;
}
const NavItem: FC<IProps> = ({ uid, mid = 0, unreads, setFiles }) => {
const { t } = useTranslation();
const [previewMsg, setPreviewMsg] = useState<ArchiveMessage>();
const { messages: normalizedMessages, normalizeMessage } = useNormalizeMessage();
const dispatch = useDispatch();
const pathMatched = useMatch(`/chat/dm/${uid}`);
const [updateReadIndex] = useReadMessageMutation();
const { currMsg, currUser } = useAppSelector((store) => {
return {
currUser: store.users.byId[uid],
currMsg: store.message[mid]
};
});
const navigate = useNavigate();
const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
const [{ isActive }, drop] = useDrop(() => ({
accept: [NativeTypes.FILE],
drop({ dataTransfer }) {
if (dataTransfer.files.length) {
setFiles([...dataTransfer.files]);
navigate(`/chat/dm/${uid}`);
// 重置
setTimeout(() => {
setFiles([]);
}, 300);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver()
})
}));
useEffect(() => {
if (currMsg) {
if (currMsg.content_type == ContentTypes.archive) {
normalizeMessage(currMsg.content);
} else {
setPreviewMsg(currMsg);
}
}
}, [currMsg]);
useEffect(() => {
if (normalizedMessages) {
setPreviewMsg(normalizedMessages?.pop());
}
}, [normalizedMessages]);
const handleReadAll = () => {
const param = { users: [{ uid, mid }] };
updateReadIndex(param);
};
const handleRemoveSession = () => {
dispatch(removeUserSession(uid));
if (pathMatched) {
navigate("/chat");
}
};
if (!currUser) return null;
return (
<Tippy
interactive
popperOptions={{ strategy: "fixed" }}
placement="right-start"
visible={contextMenuVisible}
followCursor={"initial"}
onClickOutside={hideContextMenu}
key={uid}
content={
<ContextMenu
hideMenu={hideContextMenu}
items={[
{
title: t("action.mark_read"),
handler: handleReadAll
},
{
title: t("action.hide_session"),
danger: true,
handler: handleRemoveSession
}
]}
/>
}
>
<NavLink
ref={drop}
key={uid}
className={`link session ${isActive ? "drop_over" : ""}`}
to={`/chat/dm/${uid}`}
onContextMenu={handleContextMenuEvent}
>
<User compact interactive={false} uid={uid} />
<div className="details">
<div className="up">
<span className="name">{currUser.name}</span>
{previewMsg && <time>{dayjs(previewMsg.created_at).fromNow()}</time>}
</div>
<div className="down">
<div className="msg">{renderPreviewMessage(previewMsg)}</div>
{unreads > 0 && (
<i className={`badge ${unreads > 99 ? "dot" : ""}`}>
{unreads > 99 ? null : unreads}
</i>
)}
</div>
</div>
</NavLink>
</Tippy>
);
};
export default NavItem;
-59
View File
@@ -1,59 +0,0 @@
import { FC } from "react";
import { getUnreadCount } from "../utils";
import NavItem from "./NavItem";
import { useAppSelector } from "../../../app/store";
interface Props {
uids: number[];
setDropFiles: (files: File[]) => void;
}
const DMList: FC<Props> = ({ uids, setDropFiles }) => {
const {
userMessage,
messageData,
readUsers,
loginUid = 0
} = useAppSelector((store) => {
return {
loginUid: store.authData.user?.uid,
readUsers: store.footprint.readUsers,
userData: store.users.byId,
userMessage: store.userMessage.byId,
messageData: store.message
};
});
const sessions = uids.map((uid) => {
const mids = userMessage[uid] || [];
if (mids.length == 0) {
return { lastMid: null, unreads: 0, uid };
}
const lastMid = [...mids].sort().pop();
const readIndex = readUsers[uid];
const { unreads = 0 } = getUnreadCount({
mids,
readIndex,
messageData,
loginUid
});
return { lastMid, unreads, uid };
});
// console.log("temp uids", sessions);
return (
<>
{sessions
.sort((s1, s2) => {
if (!s1.lastMid) return s2.lastMid - Infinity;
return s2.lastMid - s1.lastMid;
})
.map(({ lastMid, uid, unreads }) => {
return (
<NavItem key={uid} uid={uid} mid={lastMid} unreads={unreads} setFiles={setDropFiles} />
);
})}
</>
);
};
export default DMList;
+9 -22
View File
@@ -1,23 +1,10 @@
import { MouseEvent, FC } from "react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import FavoredMessage from "../../common/component/Message/FavoredMessage";
import IconSurprise from "../../assets/icons/emoji.surprise.svg";
import IconRemove from "../../assets/icons/close.svg";
import useFavMessage from "../../common/hook/useFavMessage";
import { useTranslation } from "react-i18next";
// 覆盖样式
const Styled = styled.div`
.favorite {
background: none;
&:hover{
background: #f5f6f7;
}
.down img {
width: 100% !important;
height: auto !important;
}
}
`;
type Props = { cid?: number; uid?: number };
const FavList: FC<Props> = ({ cid = null, uid = null }) => {
const { t } = useTranslation("chat");
@@ -29,22 +16,22 @@ const FavList: FC<Props> = ({ cid = null, uid = null }) => {
};
const noFavs = favorites.length == 0;
return (
<Styled className="p-4 bg-slate-50 rounded-xl min-w-[500px] max-h-[500px] overflow-auto drop-shadow-[0px_25px_50px_rgba(31,_41,_55,_0.25)]">
<h4 className="font-bold text-base text-gray-600 mb-4">{t('fav_msg')}({favorites.length})</h4>
<div className="p-4 bg-slate-50 dark:bg-slate-800 rounded-xl min-w-[500px] max-h-[500px] overflow-auto drop-shadow-[0px_25px_50px_rgba(31,_41,_55,_0.25)]">
<h4 className="font-bold text-base text-gray-600 dark:text-gray-400 mb-4">{t('fav_msg')}({favorites.length})</h4>
{noFavs ? (
<div className="flex flex-col gap-2 w-full items-center p-4">
<IconSurprise />
<div className="w-60 text-base text-gray-600 text-center font-bold">{t("fav_empty_tip")}</div>
<div className="w-60 text-base text-gray-600 dark:text-gray-400 text-center font-bold">{t("fav_empty_tip")}</div>
</div>
) : (
<ul className="flex flex-col gap-2">
{favorites.map(({ id }) => {
return (
<li key={id} className="relative border border-solid border-slate-200 rounded-md group">
<li key={id} className="relative border border-solid border-slate-200 dark:border-gray-600 rounded-md group">
<FavoredMessage id={id} />
<div className="flex items-center absolute top-2 right-2 border border-solid border-gray-300 rounded-md overflow-hidden invisible group-hover:visible">
<div className="flex items-center absolute top-2 right-2 border border-solid border-gray-300 dark:border-gray-600 rounded-md overflow-hidden invisible group-hover:visible">
<button className="flex-center w-6 h-6 p-1" data-id={id} onClick={handleRemove}>
<IconRemove className="fill-slate-900" />
<IconRemove className="fill-slate-900 dark:fill-slate-500" />
</button>
</div>
</li>
@@ -52,7 +39,7 @@ const FavList: FC<Props> = ({ cid = null, uid = null }) => {
})}
</ul>
)}
</Styled>
</div>
);
};
export default FavList;
+1 -1
View File
@@ -21,7 +21,7 @@ const GuestBlankPlaceholder = () => {
};
return (
<section className="flex flex-col items-center bg-transparent">
<h2 className="text-3xl text-gray-600 dark:text-gray-50 font-bold ">{t("welcome", { name: serverName })}</h2>
<h2 className="text-3xl text-gray-600 dark:text-gray-50 font-bold text-center">{t("welcome", { name: serverName })}</h2>
<div className="flex flex-col">
<span className="text-gray-400 dark:text-gray-200 my-3 text-sm">{t("guest_login_tip")}</span>
<Button onClick={handleSignIn} className="small">{t("sign_in")}</Button>
+7 -9
View File
@@ -1,9 +1,9 @@
import { useState, useRef, useEffect, FC, ReactElement } from "react";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import clsx from "clsx";
import ImagePreviewModal from "../../../common/component/ImagePreviewModal";
import Send from "../../../common/component/Send";
import Styled from "./styled";
import Operations from "./Operations";
import useUploadFile from "../../../common/hook/useUploadFile";
@@ -13,7 +13,6 @@ import useLicense from "../../../common/hook/useLicense";
import LicenseUpgradeTip from "./LicenseOutdatedTip";
// import { useTranslation } from "react-i18next";
import DnDTip from "./DnDTip";
import clsx from "clsx";
interface Props {
readonly?: boolean;
@@ -104,14 +103,13 @@ const Layout: FC<Props> = ({
);
}, []);
const name = context == "channel" ? channelsData[to]?.name : usersData[to]?.name;
return (
<>
{previewImage && <ImagePreviewModal data={previewImage} closeModal={closePreviewModal} />}
<Styled ref={drop} className={`${readonly ? "readonly" : ""}`}>
<article ref={drop} className={`relative w-full rounded-r-2xl`}>
{header}
<main className="main" ref={messagesContainer}>
<div className="chat">
<main className="h-full w-full flex items-start justify-between relative" ref={messagesContainer}>
<div className="rounded-br-2xl w-full flex flex-col h-[calc(100vh_-_56px_-_18px)]">
{children}
<div className={`p-4 pt-0 ${selects ? "selecting" : ""}`}>
{readonly ? (
@@ -126,13 +124,13 @@ const Layout: FC<Props> = ({
{selects && <Operations context={context} id={to} />}
</div>
</div>
{users && <div className="members hidden md:block">{users}</div>}
{aside && <div className="aside !hidden md:!block">{aside}</div>}
{users && <div className="shadow-[inset_0px_10px_2px_-10px_rgba(0,_0,_0,_0.1)] hidden md:block">{users}</div>}
{aside && <div className={clsx("p-3 absolute right-0 -top-14 translate-x-full flex-col hidden md:flex")}>{aside}</div>}
</main>
{!readonly && isActive && (
<DnDTip context={context} name={name} />
)}
</Styled>
</article>
</>
);
};
-90
View File
@@ -1,90 +0,0 @@
import styled from "styled-components";
const Styled = styled.article`
position: relative;
width: 100%;
border-top-right-radius: 16px;
border-bottom-right-radius: 16px;
> .head {
box-sizing: border-box;
height: 56px;
padding: 0 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
> .main {
height: 100%;
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
> .chat {
border-bottom-right-radius: 16px;
width: 100%;
display: flex;
flex-direction: column;
padding: 0;
height: calc(100vh - 56px - 18px);
}
.members {
box-shadow: inset 0px 10px 2px -10px rgba(0, 0, 0, 0.1);
}
> .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 {
display: flex;
flex-direction: column;
}
.tools {
gap: 24px;
.tool {
position: relative;
cursor: pointer;
&.fav svg path {
fill: #70707b;
}
&.active svg path {
fill: #3f3f46;
}
&:not(.active):hover svg path {
fill: #51525c;
}
&.badge:after {
position: absolute;
top: -8px;
right: -8px;
content: attr(data-count);
display: flex;
justify-content: center;
align-items: center;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #22ccee;
color: #fff;
font-weight: 900;
font-size: 10px;
line-height: 10px;
text-align: center;
color: #ffffff;
}
}
}
}
}
`;
export default Styled;
+4 -4
View File
@@ -129,15 +129,15 @@ const Session: FC<IProps> = ({
</div>
<div className="w-full flex flex-col justify-between">
<div className="flex items-center justify-between">
<span className={clsx(`flex items-center gap-2 font-semibold text-sm text-gray-500 dark:text-white whitespace-nowrap overflow-hidden text-ellipsis`, previewMsg.created_at && "max-w-[190px]")}>
{name} {!is_public && <IconLock />}
<span className={clsx(`flex items-center gap-1 font-semibold text-sm text-gray-500 dark:text-white whitespace-nowrap overflow-hidden text-ellipsis`, previewMsg.created_at && "max-w-[190px]")}>
{name} {!is_public && <IconLock className="dark:fill-gray-400" />}
</span>
<span className="text-xs text-gray-600 whitespace-nowrap overflow-hidden max-w-[80px] text-ellipsis">
<span className="text-xs text-gray-600 dark:text-gray-400 whitespace-nowrap overflow-hidden max-w-[80px] text-ellipsis">
{previewMsg.created_at ? dayjs(previewMsg.created_at).fromNow() : null}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500 whitespace-nowrap overflow-hidden w-36 text-ellipsis">{renderPreviewMessage(previewMsg)}</span>
<span className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap overflow-hidden w-36 text-ellipsis">{renderPreviewMessage(previewMsg)}</span>
{unreads > 0 && (
<strong className={clsx(`text-white h-5 min-w-[20px] bg-primary-400 font-bold text-[10px] rounded-[10px] flex-center`, unreads > 99 && 'w-1.5 h-1.5 p-0 min-w-[unset]', muted && "bg-gray-500")}>
{unreads > 99 ? null : unreads}
+7 -6
View File
@@ -1,7 +1,7 @@
import { memo, useState } from "react";
import { useParams } from "react-router-dom";
import clsx from "clsx";
import StyledWrapper from "./styled";
import BlankPlaceholder from "../../common/component/BlankPlaceholder";
import Server from "../../common/component/Server";
import ChannelChat from "./ChannelChat";
@@ -14,7 +14,7 @@ import GuestBlankPlaceholder from "./GuestBlankPlaceholder";
import GuestChannelChat from "./GuestChannelChat";
import GuestSessionList from "./GuestSessionList";
import IconList from '../../assets/icons/list.svg';
// import IconClose from '../../assets/icons/close.svg';
function ChatPage() {
const [sessionListVisible, setSessionListVisible] = useState(false);
const [channelModalVisible, setChannelModalVisible] = useState(false);
@@ -46,22 +46,23 @@ function ChatPage() {
};
// console.log("temp uid", tmpUid);
const placeholderVisible = channel_id == 0 && user_id == 0;
return (
<>
{channelModalVisible && (
<ChannelModal closeModal={toggleChannelModalVisible} personal={true} />
)}
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
<StyledWrapper className={`${isGuest ? "!pr-1 !pt-0" : ""} md:!pr-12 `}>
<div className={`flex h-full pt-2 pb-2.5 pr-1 ${isGuest ? "!pr-1 !pt-0" : ""} md:!pr-12 `}>
{sessionListVisible && <div onClick={toggleSessionList} className="z-30 fixed top-0 left-4 w-screen h-screen bg-black/50 transition-all backdrop-blur-sm"></div>}
<div className={clsx("left fixed md:relative top-0 left-0 z-40 transition-all md:overflow-auto bg-white dark:!bg-[#1F2A37]", sessionListVisible ? "max-md:translate-x-0" : "max-md:-translate-x-full")}>
<div className={clsx("flex-col rounded-l-2xl min-w-[268px] h-full border border-solid border-r-black/5 dark:border-none box-border fixed md:relative top-0 left-0 z-40 transition-all md:overflow-auto bg-white dark:!bg-[#1F2A37]", sessionListVisible ? "max-md:translate-x-0" : "max-md:-translate-x-full")}>
<Server readonly={isGuest} />
{isGuest ? <GuestSessionList /> : <SessionList tempSession={tmpSession} />}
{sessionListVisible ? null : <button className="absolute top-2 -right-[52px] z-50 p-2 bg-none md:hidden" onClick={toggleSessionList}>
<IconList className="dark:stroke-gray-300" />
</button>}
</div>
<div className={`right ${placeholderVisible ? "placeholder" : ""} bg-white dark:!bg-[#384250]`}>
<div className={`rounded-r-2xl w-full ${placeholderVisible ? "h-full flex-center" : ""} bg-white dark:!bg-[#384250]`}>
{placeholderVisible && (isGuest ? <GuestBlankPlaceholder /> : <BlankPlaceholder />)}
{channel_id !== 0 &&
(isGuest ? (
@@ -71,7 +72,7 @@ function ChatPage() {
))}
{user_id !== 0 && <DMChat uid={+user_id} />}
</div>
</StyledWrapper>
</div>
</>
);
}
-157
View File
@@ -1,157 +0,0 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
height: 100%;
padding-top: 8px;
padding-bottom: 10px;
padding-right: 4px;
> .left {
flex-direction: column;
min-width: 268px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.05);
height: 100%;
border-radius: 16px 0 0 16px;
.list {
margin: 12px 8px;
&.dms {
flex: 1;
}
.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;
}
.icon {
transition: transform 0.5s ease;
transform-origin: center;
}
.add_icon {
width: 18px;
height: 18px;
}
}
> .nav {
display: flex;
flex-direction: column;
gap: 4px;
a {
text-decoration: none;
}
.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);
}
.details {
display: flex;
flex-direction: column;
width: 100%;
.up {
display: flex;
justify-content: space-between;
align-items: center;
.name {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #52525b;
max-width: 112px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
time {
white-space: nowrap;
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: #78787c;
}
}
.down {
display: flex;
justify-content: space-between;
.msg {
min-height: 18px;
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #78787c;
white-space: nowrap;
overflow: hidden;
width: 140px;
text-overflow: ellipsis;
}
> .badge {
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;
&.dot {
min-width: unset;
width: 6px;
height: 6px;
padding: 0;
}
&.mute {
background: #bfbfbf;
}
}
}
}
}
/* drop files effect */
.drop_over {
box-shadow: inset 0 0 0 2px #52edff;
}
}
&.collapse {
.title .icon {
transform: rotate(-90deg);
}
> .nav > .link:not(.active) {
display: none;
}
}
}
}
> .right {
border-radius: 0 16px 16px 0;
width: 100%;
&.placeholder {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
`;
export default StyledWrapper;