refactor: useSelector

This commit is contained in:
Tristan Yang
2023-08-29 11:33:23 +08:00
parent 647bae7116
commit fe10a1c374
116 changed files with 572 additions and 661 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "vocechat-web", "name": "vocechat-web",
"version": "0.4.23", "version": "0.5.0",
"homepage": "https://voce.chat", "homepage": "https://voce.chat",
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^2.0.0", "@electron-toolkit/preload": "^2.0.0",
+3 -2
View File
@@ -11,10 +11,11 @@ import ChannelModal from "./ChannelModal";
import InviteModal from "./InviteModal"; import InviteModal from "./InviteModal";
import SearchUser from "./SearchUser"; import SearchUser from "./SearchUser";
import UsersModal from "./UsersModal"; import UsersModal from "./UsersModal";
import { shallowEqual } from "react-redux";
export default function AddEntriesMenu() { export default function AddEntriesMenu() {
const { t } = useTranslation(); const { t } = useTranslation();
const currentUser = useAppSelector((store) => store.authData.user); const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
const [isPrivate, setIsPrivate] = useState(false); const [isPrivate, setIsPrivate] = useState(false);
const [inviteModalVisible, setInviteModalVisible] = useState(false); const [inviteModalVisible, setInviteModalVisible] = useState(false);
const [searchModalVisible, setSearchModalVisible] = useState(false); const [searchModalVisible, setSearchModalVisible] = useState(false);
@@ -60,7 +61,7 @@ export default function AddEntriesMenu() {
<> <>
<ul className="flex flex-col rounded-xl drop-shadow p-1 select-none text-gray-500 dark:text-gray-300 bg-white dark:bg-black"> <ul className="flex flex-col rounded-xl drop-shadow p-1 select-none text-gray-500 dark:text-gray-300 bg-white dark:bg-black">
{/* temp remove public channel */} {/* temp remove public channel */}
{currentUser?.is_admin && ( {isAdmin && (
<li className={itemClass} onClick={handleOpenChannelModal.bind(null, false)}> <li className={itemClass} onClick={handleOpenChannelModal.bind(null, false)}>
<ChannelIcon className={iconClass} /> <ChannelIcon className={iconClass} />
{t("action.new_channel")} {t("action.new_channel")}
+10 -7
View File
@@ -9,6 +9,7 @@ import { ChatContext } from "../types/common";
import SaveTip from "./SaveTip"; import SaveTip from "./SaveTip";
import StyledButton from "./styled/Button"; import StyledButton from "./styled/Button";
import StyledRadio from "./styled/Radio"; import StyledRadio from "./styled/Radio";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
id: number; id: number;
@@ -16,16 +17,18 @@ type Props = {
// expires_in?: number // expires_in?: number
}; };
const AutoDeleteMessages = ({ id, type = "channel" }: Props) => { const AutoDeleteMessages = ({ id, type = "channel" }: Props) => {
const { setting, channel, loginUser } = useAppSelector((store) => { const setting = useAppSelector(
return { (store) =>
setting:
type == "channel" type == "channel"
? store.footprint.autoDeleteMsgChannels.find((item) => item.gid == id) ? store.footprint.autoDeleteMsgChannels.find((item) => item.gid == id)
: store.footprint.autoDeleteMsgUsers.find((item) => item.uid == id), : store.footprint.autoDeleteMsgUsers.find((item) => item.uid == id),
loginUser: store.authData.user, shallowEqual
channel: type == "channel" ? store.channels.byId[id] : null );
}; const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
}); const channel = useAppSelector(
(store) => (type == "channel" ? store.channels.byId[id] : null),
shallowEqual
);
const [updateSetting, { isSuccess }] = useUpdateAutoDeleteMsgMutation(); const [updateSetting, { isSuccess }] = useUpdateAutoDeleteMsgMutation();
const [clearMessage, { isSuccess: clearSuccess }] = useLazyClearChannelMessageQuery(); const [clearMessage, { isSuccess: clearSuccess }] = useLazyClearChannelMessageQuery();
+4 -8
View File
@@ -12,6 +12,7 @@ import IconAsk from "@/assets/icons/placeholder.question.svg";
import ChannelModal from "./ChannelModal"; import ChannelModal from "./ChannelModal";
import InviteModal from "./InviteModal"; import InviteModal from "./InviteModal";
import UsersModal from "./UsersModal"; import UsersModal from "./UsersModal";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
type?: "chat" | "user"; type?: "chat" | "user";
@@ -23,13 +24,8 @@ const classes = {
}; };
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => { const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
const { t } = useTranslation("welcome"); const { t } = useTranslation("welcome");
const { server, isAdmin, upgraded } = useAppSelector((store) => { const server = useAppSelector((store) => store.server, shallowEqual);
return { const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
server: store.server,
isAdmin: store.authData.user?.is_admin,
upgraded: store.server.upgraded
};
});
const [inviteModalVisible, setInviteModalVisible] = useState(false); const [inviteModalVisible, setInviteModalVisible] = useState(false);
const [createChannelVisible, setCreateChannelVisible] = useState(false); const [createChannelVisible, setCreateChannelVisible] = useState(false);
const [userListVisible, setUserListVisible] = useState(false); const [userListVisible, setUserListVisible] = useState(false);
@@ -93,7 +89,7 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
<IconChat className={classes.boxIcon} /> <IconChat className={classes.boxIcon} />
<div className={classes.boxTip}>{chatTip}</div> <div className={classes.boxTip}>{chatTip}</div>
</button> </button>
{!upgraded && ( {!server.upgraded && (
<> <>
<a <a
href={"https://voce.chat#download"} href={"https://voce.chat#download"}
+3 -6
View File
@@ -3,6 +3,7 @@ import clsx from "clsx";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import Avatar from "./Avatar"; import Avatar from "./Avatar";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
interactive?: boolean; interactive?: boolean;
@@ -12,12 +13,8 @@ interface Props {
} }
const Channel: FC<Props> = ({ interactive = true, id, compact = false, avatarSize = 32 }) => { const Channel: FC<Props> = ({ interactive = true, id, compact = false, avatarSize = 32 }) => {
const { channel, totalMemberCount } = useAppSelector((store) => { const channel = useAppSelector((store) => store.channels.byId[id], shallowEqual);
return { const totalMemberCount = useAppSelector((store) => store.users.ids.length, shallowEqual);
channel: store.channels.byId[id],
totalMemberCount: store.users.ids.length
};
});
if (!channel) return null; if (!channel) return null;
const { name, members = [], is_public, icon } = channel; const { name, members = [], is_public, icon } = channel;
+3 -3
View File
@@ -15,6 +15,7 @@ import Button from "../styled/Button";
import StyledCheckbox from "../styled/Checkbox"; import StyledCheckbox from "../styled/Checkbox";
import StyledToggle from "../styled/Toggle"; import StyledToggle from "../styled/Toggle";
import User from "../User"; import User from "../User";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
personal?: boolean; personal?: boolean;
@@ -25,9 +26,8 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
const { t } = useTranslation("chat"); const { t } = useTranslation("chat");
const navigateTo = useNavigate(); const navigateTo = useNavigate();
const [sendMessage] = useSendChannelMsgMutation(); const [sendMessage] = useSendChannelMsgMutation();
const { loginUser, channelData } = useAppSelector((store) => { const channelData = useAppSelector((store) => store.channels.byId, shallowEqual);
return { loginUser: store.authData.user, channelData: store.channels.byId }; const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
});
const [data, setData] = useState<CreateChannelDTO>({ const [data, setData] = useState<CreateChannelDTO>({
name: "", name: "",
description: "", description: "",
+2 -1
View File
@@ -13,6 +13,7 @@ import {
PdfPreview, PdfPreview,
VideoPreview VideoPreview
} from "./preview"; } from "./preview";
import { shallowEqual } from "react-redux";
interface Data { interface Data {
file_type: string; file_type: string;
@@ -82,7 +83,7 @@ const FileBox: FC<Props> = ({
}) => { }) => {
const [fetchError, setFetchError] = useState(false); const [fetchError, setFetchError] = useState(false);
const { isExpired, setExpired } = useExpiredResMap(); const { isExpired, setExpired } = useExpiredResMap();
const fromUser = useAppSelector((store) => store.users.byId[from_uid]); const fromUser = useAppSelector((store) => store.users.byId[from_uid], shallowEqual);
const icon = getFileIcon(file_type, name, "icon w-9 h-12"); const icon = getFileIcon(file_type, name, "icon w-9 h-12");
const expired = isExpired(content); const expired = isExpired(content);
useEffect(() => { useEffect(() => {
+2 -1
View File
@@ -12,6 +12,7 @@ import ExpiredMessage from "./ExpiredMessage";
import ImageMessage from "./ImageMessage"; import ImageMessage from "./ImageMessage";
import OtherFileMessage from "./OtherFileMessage"; import OtherFileMessage from "./OtherFileMessage";
import VideoMessage from "./VideoMessage"; import VideoMessage from "./VideoMessage";
import { shallowEqual } from "react-redux";
const isLocalFile = (content: string) => { const isLocalFile = (content: string) => {
return content.startsWith("blob:"); return content.startsWith("blob:");
@@ -64,7 +65,7 @@ const FileMessage: FC<Props> = ({
isSuccess: uploadSuccess, isSuccess: uploadSuccess,
isError isError
} = useUploadFile(); } = useUploadFile();
const fromUser = useAppSelector((store) => store.users.byId[from_uid]); const fromUser = useAppSelector((store) => store.users.byId[from_uid], shallowEqual);
const { size = 0, name, content_type } = properties ?? {}; const { size = 0, name, content_type } = properties ?? {};
useEffect(() => { useEffect(() => {
const handleUpSend = async ({ const handleUpSend = async ({
+2 -1
View File
@@ -4,6 +4,7 @@ import { Navigate } from "react-router-dom";
import { useGetInitializedQuery, useLazyGuestLoginQuery } from "@/app/services/auth"; import { useGetInitializedQuery, useLazyGuestLoginQuery } from "@/app/services/auth";
import { useGetLoginConfigQuery } from "@/app/services/server"; import { useGetLoginConfigQuery } from "@/app/services/server";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
children: ReactElement; children: ReactElement;
@@ -12,7 +13,7 @@ const GuestOnly: FC<Props> = ({ children }) => {
const { data: loginConfig, isLoading: fetchingConfig } = useGetLoginConfigQuery(); const { data: loginConfig, isLoading: fetchingConfig } = useGetLoginConfigQuery();
const { isLoading: initChecking } = useGetInitializedQuery(); const { isLoading: initChecking } = useGetInitializedQuery();
const [guestLogin, { isLoading: guestSigning }] = useLazyGuestLoginQuery(); const [guestLogin, { isLoading: guestSigning }] = useLazyGuestLoginQuery();
const { token, user, initialized } = useAppSelector((store) => store.authData); const { token, user, initialized } = useAppSelector((store) => store.authData, shallowEqual);
useEffect(() => { useEffect(() => {
// 未登录 // 未登录
+3 -6
View File
@@ -9,6 +9,7 @@ import Button from "../styled/Button";
import StyledCheckbox from "../styled/Checkbox"; import StyledCheckbox from "../styled/Checkbox";
import Input from "../styled/Input"; import Input from "../styled/Input";
import User from "../User"; import User from "../User";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
cid?: number; cid?: number;
@@ -18,12 +19,8 @@ interface Props {
const AddMembers: FC<Props> = ({ cid = 0, closeModal }) => { const AddMembers: FC<Props> = ({ cid = 0, closeModal }) => {
const [addMembers, { isLoading: isAdding, isSuccess }] = useAddMembersMutation(); const [addMembers, { isLoading: isAdding, isSuccess }] = useAddMembersMutation();
const [selects, setSelects] = useState<number[]>([]); const [selects, setSelects] = useState<number[]>([]);
const { channel, userData } = useAppSelector((store) => { const channel = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
return { const userData = useAppSelector((store) => store.users.byId, shallowEqual);
channel: store.channels.byId[cid],
userData: store.users.byId
};
});
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
toast.success("Add members successfully!"); toast.success("Add members successfully!");
+7 -7
View File
@@ -6,6 +6,7 @@ import CloseIcon from "@/assets/icons/close.svg";
import Modal from "../Modal"; import Modal from "../Modal";
import AddMembers from "./AddMembers"; import AddMembers from "./AddMembers";
import InviteByEmail from "./InviteByEmail"; import InviteByEmail from "./InviteByEmail";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
type?: "server" | "channel"; type?: "server" | "channel";
@@ -16,13 +17,12 @@ interface Props {
const InviteModal: FC<Props> = ({ type = "server", cid, title = "", closeModal }) => { const InviteModal: FC<Props> = ({ type = "server", cid, title = "", closeModal }) => {
const { t } = useTranslation("chat"); const { t } = useTranslation("chat");
const { channel, server } = useAppSelector((store) => { const channel = useAppSelector(
return { (store) => (cid ? store.channels.byId[cid] : undefined),
channel: cid ? store.channels.byId[cid] : undefined, shallowEqual
server: store.server );
}; const serverName = useAppSelector((store) => store.server.name, shallowEqual);
}); const finalTitle = type == "server" ? serverName : `#${title || channel?.name}`;
const finalTitle = type == "server" ? server.name : `#${title || channel?.name}`;
return ( return (
<Modal> <Modal>
<div className="flex flex-col bg-white dark:bg-gray-900 rounded-lg max-h-[85vh] overflow-y-scroll md:min-w-[408px] relative"> <div className="flex flex-col bg-white dark:bg-gray-900 rounded-lg max-h-[85vh] overflow-y-scroll md:min-w-[408px] relative">
+4 -7
View File
@@ -15,6 +15,7 @@ import IconCheck from "@/assets/icons/check.sign.svg";
import IconMore from "@/assets/icons/more.svg"; import IconMore from "@/assets/icons/more.svg";
import IconOwner from "@/assets/icons/owner.svg"; import IconOwner from "@/assets/icons/owner.svg";
import User from "../User"; import User from "../User";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
cid?: number; cid?: number;
@@ -23,13 +24,9 @@ const MemberList: FC<Props> = ({ cid }) => {
const ref = useRef<HTMLUListElement | null>(null); const ref = useRef<HTMLUListElement | null>(null);
const { t } = useTranslation("member"); const { t } = useTranslation("member");
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
const { userMap, channels, loginUser } = useAppSelector((store) => { const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
return { const userMap = useAppSelector((store) => store.users.byId, shallowEqual);
userMap: store.users.byId, const channels = useAppSelector((store) => store.channels, shallowEqual);
channels: store.channels,
loginUser: store.authData.user
};
});
const { uids, input, updateInput } = useFilteredUsers(); const { uids, input, updateInput } = useFilteredUsers();
const { copyEmail, removeFromChannel, removeUser } = useUserOperation({ cid }); const { copyEmail, removeFromChannel, removeUser } = useUserOperation({ cid });
const [updateUser, { isSuccess: updateSuccess }] = useUpdateUserMutation(); const [updateUser, { isSuccess: updateSuccess }] = useUpdateUserMutation();
+3 -6
View File
@@ -3,21 +3,18 @@ import { useTranslation } from "react-i18next";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import InviteLink from "../InviteLink"; import InviteLink from "../InviteLink";
import MemberList from "./MemberList"; import MemberList from "./MemberList";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
cid?: number; cid?: number;
} }
const ManageMembers: FC<Props> = ({ cid }) => { const ManageMembers: FC<Props> = ({ cid }) => {
const { t } = useTranslation("member"); const { t } = useTranslation("member");
const { loginUser } = useAppSelector((store) => { const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
return {
loginUser: store.authData.user
};
});
return ( return (
<section className="flex flex-col w-full"> <section className="flex flex-col w-full">
{loginUser?.is_admin && <InviteLink />} {isAdmin && <InviteLink />}
<div className="flex flex-col mb-10"> <div className="flex flex-col mb-10">
<h4 className="font-bold text-gray-700 dark:text-white">{t("manage_members")}</h4> <h4 className="font-bold text-gray-700 dark:text-white">{t("manage_members")}</h4>
<p className="text-gray-400 dark:text-gray-100 text-xs">{t("manage_tip")}</p> <p className="text-gray-400 dark:text-gray-100 text-xs">{t("manage_tip")}</p>
+2 -1
View File
@@ -5,6 +5,7 @@ import { useKey } from "rooks";
import { ContentTypes } from "@/app/config"; import { ContentTypes } from "@/app/config";
import { useEditMessageMutation } from "@/app/services/message"; import { useEditMessageMutation } from "@/app/services/message";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
mid: number; mid: number;
@@ -12,7 +13,7 @@ type Props = {
}; };
const EditMessage: FC<Props> = ({ mid, cancelEdit }) => { const EditMessage: FC<Props> = ({ mid, cancelEdit }) => {
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const msg = useAppSelector((store) => store.message[mid]); const msg = useAppSelector((store) => store.message[mid], shallowEqual);
const [shift, setShift] = useState(false); const [shift, setShift] = useState(false);
const [enter, setEnter] = useState(false); const [enter, setEnter] = useState(false);
const [currMsg, setCurrMsg] = useState(msg?.content); const [currMsg, setCurrMsg] = useState(msg?.content);
+2 -1
View File
@@ -3,6 +3,7 @@ import Tippy from "@tippyjs/react";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import Profile from "../Profile"; import Profile from "../Profile";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
uid: number; uid: number;
@@ -12,7 +13,7 @@ interface Props {
} }
const Mention = ({ uid, popover = true, cid, textOnly = false }: Props) => { const Mention = ({ uid, popover = true, cid, textOnly = false }: Props) => {
const usersData = useAppSelector((store) => store.users.byId); const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
const user = usersData[uid]; const user = usersData[uid];
if (!user) return null; if (!user) return null;
if (textOnly) return <>{`@${user.name}`}</>; if (textOnly) return <>{`@${user.name}`}</>;
+3 -3
View File
@@ -4,6 +4,7 @@ import clsx from "clsx";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import Avatar from "../Avatar"; import Avatar from "../Avatar";
import renderContent from "./renderContent"; import renderContent from "./renderContent";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
mid?: number; mid?: number;
@@ -11,9 +12,8 @@ interface Props {
} }
const PreviewMessage: FC<Props> = ({ mid = 0, context = "forward" }) => { const PreviewMessage: FC<Props> = ({ mid = 0, context = "forward" }) => {
const { msg, usersData } = useAppSelector((store) => { const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
return { msg: store.message[mid], usersData: store.users.byId }; const msg = useAppSelector((store) => store.message[mid], shallowEqual);
});
if (!msg) return null; if (!msg) return null;
const { from_uid = 0, content_type, content, thumbnail = "", properties } = msg; const { from_uid = 0, content_type, content, thumbnail = "", properties } = msg;
const { name, avatar } = usersData[from_uid] ?? {}; const { name, avatar } = usersData[from_uid] ?? {};
+3 -6
View File
@@ -8,6 +8,7 @@ import IconAddEmoji from "@/assets/icons/add.emoji.svg";
import ReactionItem, { Emojis, ReactionMap } from "../ReactionItem"; import ReactionItem, { Emojis, ReactionMap } from "../ReactionItem";
import Tooltip from "../Tooltip"; import Tooltip from "../Tooltip";
import ReactionPicker from "./ReactionPicker"; import ReactionPicker from "./ReactionPicker";
import { shallowEqual } from "react-redux";
const ReactionDetails = ({ const ReactionDetails = ({
uids = [], uids = [],
@@ -18,7 +19,7 @@ const ReactionDetails = ({
emoji: keyof Emojis; emoji: keyof Emojis;
index: number; index: number;
}) => { }) => {
const usersData = useAppSelector((store) => store.users.byId); const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
const names = uids.map((id) => { const names = uids.map((id) => {
return usersData[id]?.name ?? "Deleted User"; return usersData[id]?.name ?? "Deleted User";
}); });
@@ -52,11 +53,7 @@ type Props = {
}; };
const Reaction: FC<Props> = ({ mid, reactions = null, readOnly = false }) => { const Reaction: FC<Props> = ({ mid, reactions = null, readOnly = false }) => {
const [reactWithEmoji] = useReactMessageMutation(); const [reactWithEmoji] = useReactMessageMutation();
const { currUid } = useAppSelector((store) => { const currUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
return {
currUid: store.authData.user?.uid
};
});
const handleReact = (emoji: string) => { const handleReact = (emoji: string) => {
reactWithEmoji({ mid, action: emoji }); reactWithEmoji({ mid, action: emoji });
}; };
+3 -6
View File
@@ -4,6 +4,7 @@ import { Emojis } from "@/app/config";
import { useReactMessageMutation } from "@/app/services/message"; import { useReactMessageMutation } from "@/app/services/message";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import Emoji from "../ReactionItem"; import Emoji from "../ReactionItem";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
mid: number; mid: number;
@@ -11,12 +12,8 @@ type Props = {
}; };
const ReactionPicker: FC<Props> = ({ mid, hidePicker }) => { const ReactionPicker: FC<Props> = ({ mid, hidePicker }) => {
const [reactMessage, { isLoading }] = useReactMessageMutation(); const [reactMessage, { isLoading }] = useReactMessageMutation();
const { reactionData, currUid } = useAppSelector((store) => { const currUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
return { const reactionData = useAppSelector((store) => store.reactionMessage[mid] || {}, shallowEqual);
reactionData: store.reactionMessage[mid] || {},
currUid: store.authData.user?.uid
};
});
const handleReact = (emoji: string) => { const handleReact = (emoji: string) => {
reactMessage({ mid, action: emoji }); reactMessage({ mid, action: emoji });
hidePicker(); hidePicker();
+3 -3
View File
@@ -11,6 +11,7 @@ import Avatar from "../Avatar";
import LinkifyText from "../LinkifyText"; import LinkifyText from "../LinkifyText";
import MarkdownRender from "../MarkdownRender"; import MarkdownRender from "../MarkdownRender";
import ForwardedMessage from "./ForwardedMessage"; import ForwardedMessage from "./ForwardedMessage";
import { shallowEqual } from "react-redux";
const renderContent = (data: MessagePayload, context: ChatContext, to: number) => { const renderContent = (data: MessagePayload, context: ChatContext, to: number) => {
const { content_type, content, thumbnail, properties, created_at, from_uid = 0 } = data; const { content_type, content, thumbnail, properties, created_at, from_uid = 0 } = data;
@@ -86,9 +87,8 @@ interface ReplyProps {
const Reply: FC<ReplyProps> = ({ mid, interactive = true, context, to = 0 }) => { const Reply: FC<ReplyProps> = ({ mid, interactive = true, context, to = 0 }) => {
const { t } = useTranslation("chat"); const { t } = useTranslation("chat");
const { data, users } = useAppSelector((store) => { const users = useAppSelector((store) => store.users.byId, shallowEqual);
return { data: store.message[mid], users: store.users.byId }; const data = useAppSelector((store) => store.message[mid], shallowEqual);
});
const handleClick = (evt: MouseEvent<HTMLDivElement>) => { const handleClick = (evt: MouseEvent<HTMLDivElement>) => {
const { mid } = evt.currentTarget.dataset; const { mid } = evt.currentTarget.dataset;
const msgEle = document.querySelector<HTMLDivElement>(`[data-msg-mid='${mid}']`); const msgEle = document.querySelector<HTMLDivElement>(`[data-msg-mid='${mid}']`);
+2 -2
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { useLazyGetOGInfoQuery } from "@/app/services/message"; import { useLazyGetOGInfoQuery } from "@/app/services/message";
import { upsertOG } from "@/app/slices/footprint"; import { upsertOG } from "@/app/slices/footprint";
@@ -10,7 +10,7 @@ export default function URLPreview({ url = "" }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [favicon, setFavicon] = useState(""); const [favicon, setFavicon] = useState("");
const [getInfo, { isLoading }] = useLazyGetOGInfoQuery(); const [getInfo, { isLoading }] = useLazyGetOGInfoQuery();
const ogData = useAppSelector((store) => store.footprint.og[url]); const ogData = useAppSelector((store) => store.footprint.og[url], shallowEqual);
const [data, setData] = useState<{ title: string; description: string; ogImage: string } | null>( const [data, setData] = useState<{ title: string; description: string; ogImage: string } | null>(
null null
); );
+8 -10
View File
@@ -19,6 +19,7 @@ import Reaction from "./Reaction";
import renderContent from "./renderContent"; import renderContent from "./renderContent";
import Reply from "./Reply"; import Reply from "./Reply";
import useInView from "./useInView"; import useInView from "./useInView";
import { shallowEqual } from "react-redux";
interface IProps { interface IProps {
readOnly?: boolean; readOnly?: boolean;
@@ -41,17 +42,14 @@ const Message: FC<IProps> = ({
const [edit, setEdit] = useState(false); const [edit, setEdit] = useState(false);
const avatarRef = useRef(null); const avatarRef = useRef(null);
const { getPinInfo } = usePinMessage(context == "channel" ? contextId : 0); const { getPinInfo } = usePinMessage(context == "channel" ? contextId : 0);
const { message, reactionMessageData, usersData, loginUid, enableRightLayout } = useAppSelector( const message = useAppSelector((store) => store.message[mid], shallowEqual);
(store) => { const enableRightLayout = useAppSelector(
return { (store) => store.server.chat_layout_mode == "SelfRight",
enableRightLayout: store.server.chat_layout_mode == "SelfRight", shallowEqual
reactionMessageData: store.reactionMessage,
message: store.message[mid],
usersData: store.users.byId,
loginUid: store.authData.user?.uid
};
}
); );
const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
const reactionMessageData = useAppSelector((store) => store.reactionMessage, shallowEqual);
const toggleEditMessage = () => { const toggleEditMessage = () => {
setEdit((prev) => !prev); setEdit((prev) => !prev);
@@ -9,6 +9,7 @@ import usePinMessage from "@/hooks/usePinMessage";
import DeleteMessageConfirm from "../DeleteMessageConfirm"; import DeleteMessageConfirm from "../DeleteMessageConfirm";
import ForwardModal from "../ForwardModal"; import ForwardModal from "../ForwardModal";
import PinMessageModal from "./PinMessageModal"; import PinMessageModal from "./PinMessageModal";
import { shallowEqual } from "react-redux";
interface Params { interface Params {
mid: number; mid: number;
@@ -18,13 +19,12 @@ interface Params {
export default function useMessageOperation({ mid, context, contextId }: Params) { export default function useMessageOperation({ mid, context, contextId }: Params) {
const { copy } = useCopy(); const { copy } = useCopy();
const { loginUser, message, channel } = useAppSelector((store) => { const message = useAppSelector((store) => store.message[mid], shallowEqual);
return { const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
channel: context == "channel" ? store.channels.byId[contextId] : undefined, const channel = useAppSelector(
message: store.message[mid], (store) => (context == "channel" ? store.channels.byId[contextId] : undefined),
loginUser: store.authData.user shallowEqual
}; );
});
const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage( const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage(
context == "channel" ? contextId : 0 context == "channel" ? contextId : 0
); );
+2 -1
View File
@@ -2,11 +2,12 @@ import { FC } from "react";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { useGetServerQuery } from "@/app/services/server"; import { useGetServerQuery } from "@/app/services/server";
import { shallowEqual } from "react-redux";
type Props = {}; type Props = {};
const Meta: FC<Props> = () => { const Meta: FC<Props> = () => {
useGetServerQuery(); useGetServerQuery();
const { name, logo } = useAppSelector((store) => store.server); const { name, logo } = useAppSelector((store) => store.server, shallowEqual);
return ( return (
<Helmet> <Helmet>
{name && <title>{name} Web App</title>} {name && <title>{name} Web App</title>}
+2 -1
View File
@@ -25,6 +25,7 @@ import useUploadFile from "@/hooks/useUploadFile";
import { isMobile } from "@/utils"; import { isMobile } from "@/utils";
import User from "../User"; import User from "../User";
import { CONFIG } from "./config"; import { CONFIG } from "./config";
import { shallowEqual } from "react-redux";
export const TEXT_EDITOR_PREFIX = "_text_editor"; export const TEXT_EDITOR_PREFIX = "_text_editor";
@@ -55,7 +56,7 @@ const Plugins: FC<Props> = ({
const [context, to] = id.split("_") as [ChatContext, number]; const [context, to] = id.split("_") as [ChatContext, number];
const { addStageFile } = useUploadFile({ context, id: to }); const { addStageFile } = useUploadFile({ context, id: to });
const enableMentions = members.length > 0; const enableMentions = members.length > 0;
const userData = useAppSelector((store) => store.users.byId); const userData = useAppSelector((store) => store.users.byId, shallowEqual);
const editableRef = useRef(null); const editableRef = useRef(null);
const initialProps = { const initialProps = {
...CONFIG.editableProps, ...CONFIG.editableProps,
+3 -3
View File
@@ -7,15 +7,15 @@ import { PinnedMessage } from "@/types/channel";
import { normalizeFileMessage } from "../utils"; import { normalizeFileMessage } from "../utils";
import Avatar from "./Avatar"; import Avatar from "./Avatar";
import renderContent from "./Message/renderContent"; import renderContent from "./Message/renderContent";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
data: PinnedMessage; data: PinnedMessage;
} }
const PinnedMessageView: FC<Props> = ({ data }) => { const PinnedMessageView: FC<Props> = ({ data }) => {
const { msgData, usersData } = useAppSelector((store) => { const msgData = useAppSelector((store) => store.message, shallowEqual);
return { msgData: store.message, usersData: store.users.byId }; const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
});
// console.log("piiii", data); // console.log("piiii", data);
const { mid = 0 } = data; const { mid = 0 } = data;
+2 -5
View File
@@ -12,6 +12,7 @@ import IconMessage from "@/assets/icons/message.svg";
import IconMore from "@/assets/icons/more.svg"; import IconMore from "@/assets/icons/more.svg";
import Avatar from "../Avatar"; import Avatar from "../Avatar";
import ContextMenu, { Item } from "../ContextMenu"; import ContextMenu, { Item } from "../ContextMenu";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
uid: number; uid: number;
@@ -36,11 +37,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
updateRole updateRole
} = useUserOperation({ uid, cid }); } = useUserOperation({ uid, cid });
const { data } = useAppSelector((store) => { const data = useAppSelector((store) => store.users.byId[uid], shallowEqual);
return {
data: store.users.byId[uid]
};
});
if (!data) return null; if (!data) return null;
// console.log("profile", data); // console.log("profile", data);
const { const {
+2 -1
View File
@@ -1,6 +1,7 @@
import QR from "qrcode.react"; import QR from "qrcode.react";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
link: string; link: string;
@@ -9,7 +10,7 @@ type Props = {
}; };
const QRCode = ({ link, size = 512, level = "L" }: Props) => { const QRCode = ({ link, size = 512, level = "L" }: Props) => {
const logo = useAppSelector((store) => store.server.logo); const logo = useAppSelector((store) => store.server.logo, shallowEqual);
return ( return (
<div className="p-2 bg-white dark:bg-slate-200 rounded"> <div className="p-2 bg-white dark:bg-slate-200 rounded">
<QR <QR
+7 -11
View File
@@ -1,9 +1,10 @@
import { FC, ReactElement } from "react"; import { FC, ReactElement, memo } from "react";
import { matchRoutes, Navigate, useLocation } from "react-router-dom"; import { matchRoutes, Navigate, useLocation } from "react-router-dom";
import { GuestRoutes, KEY_LOCAL_TRY_PATH } from "@/app/config"; import { GuestRoutes, KEY_LOCAL_TRY_PATH } from "@/app/config";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import Loading from "./Loading"; import Loading from "./Loading";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
children: ReactElement; children: ReactElement;
@@ -16,15 +17,10 @@ const RequireAuth: FC<Props> = ({ children, redirectTo = "/login" }) => {
const location = useLocation(); const location = useLocation();
const matches = matchRoutes(GuestAllows, location); const matches = matchRoutes(GuestAllows, location);
const allowGuest = matches ? !!matches[0].pathname : false; const allowGuest = matches ? !!matches[0].pathname : false;
const { const token = useAppSelector((store) => store.authData.token, shallowEqual);
authData: { token, guest, initialized }, const guest = useAppSelector((store) => store.authData.guest, shallowEqual);
loginConfig const initialized = useAppSelector((store) => store.authData.initialized, shallowEqual);
} = useAppSelector((store) => { const loginConfig = useAppSelector((store) => store.server.loginConfig, shallowEqual);
return {
authData: store.authData,
loginConfig: store.server.loginConfig
};
});
console.info("check basic info", loginConfig); console.info("check basic info", loginConfig);
// 初始化login配置检查 // 初始化login配置检查
if (!loginConfig) return <Loading fullscreen={true} context="auth-route" />; if (!loginConfig) return <Loading fullscreen={true} context="auth-route" />;
@@ -55,4 +51,4 @@ const RequireAuth: FC<Props> = ({ children, redirectTo = "/login" }) => {
return children; return children;
}; };
export default RequireAuth; export default memo(RequireAuth);
+2 -1
View File
@@ -3,6 +3,7 @@ import { Navigate } from "react-router-dom";
import { useGetInitializedQuery } from "@/app/services/auth"; import { useGetInitializedQuery } from "@/app/services/auth";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
children: ReactElement; children: ReactElement;
@@ -11,7 +12,7 @@ interface Props {
const RequireNoAuth: FC<Props> = ({ children, redirectTo = "/" }) => { const RequireNoAuth: FC<Props> = ({ children, redirectTo = "/" }) => {
const { isLoading } = useGetInitializedQuery(); const { isLoading } = useGetInitializedQuery();
const { token, initialized, guest } = useAppSelector((store) => store.authData); const { token, initialized, guest } = useAppSelector((store) => store.authData, shallowEqual);
if (isLoading) return null; if (isLoading) return null;
// 未初始化 则先走setup 流程 // 未初始化 则先走setup 流程
if (!initialized) return <Navigate to={`/onboarding`} replace />; if (!initialized) return <Navigate to={`/onboarding`} replace />;
+2 -1
View File
@@ -13,6 +13,7 @@ import Avatar from "./Avatar";
import Modal from "./Modal"; import Modal from "./Modal";
import StyledButton from "./styled/Button"; import StyledButton from "./styled/Button";
import Input from "./styled/Input"; import Input from "./styled/Input";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
closeModal: () => void; closeModal: () => void;
@@ -21,7 +22,7 @@ type Type = "id" | "email" | "name";
const SearchUser: FC<Props> = ({ closeModal }) => { const SearchUser: FC<Props> = ({ closeModal }) => {
const [updateContactStatus, { isLoading: adding }] = useUpdateContactStatusMutation(); const [updateContactStatus, { isLoading: adding }] = useUpdateContactStatusMutation();
const usersData = useAppSelector((store) => store.users.byId); const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
const { t } = useTranslation(); const { t } = useTranslation();
const navigateTo = useNavigate(); const navigateTo = useNavigate();
const inputRef = useRef(null); const inputRef = useRef(null);
+3 -3
View File
@@ -8,6 +8,7 @@ import IconClose from "@/assets/icons/close.circle.svg";
import pictureIcon from "@/assets/icons/picture.svg?url"; import pictureIcon from "@/assets/icons/picture.svg?url";
import LinkifyText from "../LinkifyText"; import LinkifyText from "../LinkifyText";
import MarkdownRender from "../MarkdownRender"; import MarkdownRender from "../MarkdownRender";
import { shallowEqual } from "react-redux";
const renderContent = (data: MessagePayload) => { const renderContent = (data: MessagePayload) => {
const { content_type, content, thumbnail = "", properties } = data; const { content_type, content, thumbnail = "", properties } = data;
@@ -69,9 +70,8 @@ export default function Replying({
mid: number; mid: number;
}) { }) {
const { removeReplying } = useSendMessage({ to: id, context }); const { removeReplying } = useSendMessage({ to: id, context });
const { msg, usersData } = useAppSelector((store) => { const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
return { usersData: store.users.byId, msg: store.message[mid] }; const msg = useAppSelector((store) => store.message[mid], shallowEqual);
});
const removeReply = () => { const removeReply = () => {
removeReplying(); removeReplying();
}; };
+14 -12
View File
@@ -20,6 +20,7 @@ import Replying from "./Replying";
import Toolbar from "./Toolbar"; import Toolbar from "./Toolbar";
// import StyledSend from "./styled"; // import StyledSend from "./styled";
import UploadFileList from "./UploadFileList"; import UploadFileList from "./UploadFileList";
import { shallowEqual } from "react-redux";
const Modes = { const Modes = {
text: "text", text: "text",
@@ -48,18 +49,19 @@ const Send: FC<IProps> = ({
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const addLocalFileMessage = useAddLocalFileMessage({ context, to: id }); const addLocalFileMessage = useAddLocalFileMessage({ context, to: id });
// 谁发的 // 谁发的
const { from_uid, replying_mid, mode, uploadFiles, channelsData, usersData, uids } = const from_uid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
useAppSelector((store) => { const replying_mid = useAppSelector(
return { (store) => store.message.replying[`${context}_${id}`],
channelsData: store.channels.byId, shallowEqual
uids: store.users.ids, );
usersData: store.users.byId, const mode = useAppSelector((store) => store.ui.inputMode, shallowEqual);
mode: store.ui.inputMode, const uploadFiles = useAppSelector(
from_uid: store.authData.user?.uid, (store) => store.ui.uploadFiles[`${context}_${id}`] ?? [],
replying_mid: store.message.replying[`${context}_${id}`], shallowEqual
uploadFiles: store.ui.uploadFiles[`${context}_${id}`] ?? [] );
}; const uids = useAppSelector((store) => store.users.ids, shallowEqual);
}); const channelsData = useAppSelector((store) => store.channels.byId, shallowEqual);
const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
const { sendMessage } = useSendMessage({ context, from: from_uid, to: id }); const { sendMessage } = useSendMessage({ context, from: from_uid, to: id });
useEffect(() => { useEffect(() => {
+3 -7
View File
@@ -6,6 +6,7 @@ import { useAppSelector } from "@/app/store";
import IconAdd from "@/assets/icons/add.svg"; import IconAdd from "@/assets/icons/add.svg";
import AddEntriesMenu from "./AddEntriesMenu"; import AddEntriesMenu from "./AddEntriesMenu";
import Tooltip from "./Tooltip"; import Tooltip from "./Tooltip";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
readonly?: boolean; readonly?: boolean;
@@ -13,14 +14,9 @@ type Props = {
export default function Server({ readonly = false }: Props) { export default function Server({ readonly = false }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
const { pathname } = useLocation(); const { pathname } = useLocation();
const { server, userCount } = useAppSelector((store) => { const { name, description, logo } = useAppSelector((store) => store.server, shallowEqual);
return { const userCount = useAppSelector((store) => store.users.ids.length, shallowEqual);
userCount: store.users.ids.length,
server: store.server
};
});
// console.log("server info", server); // console.log("server info", server);
const { name, description, logo } = server;
if (readonly) if (readonly)
return ( return (
<NavLink to={"/"} className="relative flex items-center justify-between gap-2 px-4 py-2"> <NavLink to={"/"} className="relative flex items-center justify-between gap-2 px-4 py-2">
+2 -1
View File
@@ -3,6 +3,7 @@ import { Trans, useTranslation } from "react-i18next";
import { useAppSelector } from "../app/store"; import { useAppSelector } from "../app/store";
import { compareVersion } from "../utils"; import { compareVersion } from "../utils";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
empty?: boolean; empty?: boolean;
@@ -12,7 +13,7 @@ type Props = {
const ServerVersionChecker = ({ empty = false, version, children }: Props) => { const ServerVersionChecker = ({ empty = false, version, children }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const currentVersion = useAppSelector((store) => store.server.version); const currentVersion = useAppSelector((store) => store.server.version, shallowEqual);
if (!currentVersion) return null; if (!currentVersion) return null;
const res = compareVersion(currentVersion, version); const res = compareVersion(currentVersion, version);
if (res < 0) if (res < 0)
+2 -1
View File
@@ -1,11 +1,12 @@
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import clsx from "clsx"; import clsx from "clsx";
import { shallowEqual } from "react-redux";
// import React from "react"; // import React from "react";
type Props = {}; type Props = {};
const StreamStatus = (props: Props) => { const StreamStatus = (props: Props) => {
const status = useAppSelector((store) => store.ui.SSEStatus); const status = useAppSelector((store) => store.ui.SSEStatus, shallowEqual);
return ( return (
<aside className="fixed right-2 bottom-2"> <aside className="fixed right-2 bottom-2">
<div <div
+15 -27
View File
@@ -2,34 +2,21 @@ import { useEffect } from "react";
import { useAppSelector } from "../app/store"; import { useAppSelector } from "../app/store";
import getUnreadCount from "../routes/chat/utils"; import getUnreadCount from "../routes/chat/utils";
import { shallowEqual } from "react-redux";
// type Props = {} // type Props = {}
let total = 0; let total = 0;
let title = ""; let title = "";
const UnreadTabTip = () => { const UnreadTabTip = () => {
const { const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
muteChannels, const muteChannels = useAppSelector((store) => store.footprint.muteChannels, shallowEqual);
muteUsers, const muteUsers = useAppSelector((store) => store.footprint.muteUsers, shallowEqual);
userData, const readChannels = useAppSelector((store) => store.footprint.readChannels, shallowEqual);
dmMids, const readUsers = useAppSelector((store) => store.footprint.readUsers, shallowEqual);
channelMids, const userData = useAppSelector((store) => store.users.byId, shallowEqual);
messageData, const DMMap = useAppSelector((store) => store.userMessage.byId, shallowEqual);
readChannels, const channelMids = useAppSelector((store) => store.channelMessage, shallowEqual);
readUsers, const messageData = useAppSelector((store) => store.message, shallowEqual);
loginUid
} = useAppSelector((store) => {
return {
userData: store.users.byId,
dmMids: store.userMessage.byId,
channelMids: store.channelMessage,
messageData: store.message,
readChannels: store.footprint.readChannels,
readUsers: store.footprint.readUsers,
muteChannels: store.footprint.muteChannels,
muteUsers: store.footprint.muteUsers,
loginUid: store.authData.user?.uid ?? 0
};
});
useEffect(() => { useEffect(() => {
if (loginUid === 0) { if (loginUid === 0) {
@@ -40,8 +27,8 @@ const UnreadTabTip = () => {
} }
total = 0; total = 0;
// dm // dm
Object.entries(dmMids).forEach(([id, mids]) => { Object.entries(DMMap).forEach(([id, mids]) => {
if (!muteUsers[id]) { if (!muteUsers[+id]) {
if (userData[+id]) { if (userData[+id]) {
const { unreads = 0 } = getUnreadCount({ const { unreads = 0 } = getUnreadCount({
mids, mids,
@@ -55,7 +42,7 @@ const UnreadTabTip = () => {
}); });
// channel // channel
Object.entries(channelMids).map(([id, mids]) => { Object.entries(channelMids).map(([id, mids]) => {
if (!muteChannels[id]) { if (!muteChannels[+id]) {
const { unreads = 0 } = getUnreadCount({ const { unreads = 0 } = getUnreadCount({
mids, mids,
readIndex: readChannels[+id], readIndex: readChannels[+id],
@@ -86,7 +73,7 @@ const UnreadTabTip = () => {
}; };
}, [ }, [
userData, userData,
dmMids, DMMap,
channelMids, channelMids,
readChannels, readChannels,
messageData, messageData,
@@ -95,6 +82,7 @@ const UnreadTabTip = () => {
muteChannels, muteChannels,
muteUsers muteUsers
]); ]);
console.log("unread tip", total);
return null; return null;
}; };
+4 -7
View File
@@ -10,6 +10,7 @@ import IconOwner from "@/assets/icons/owner.svg";
import Avatar from "../Avatar"; import Avatar from "../Avatar";
import Profile from "../Profile"; import Profile from "../Profile";
import ContextMenu from "./ContextMenu"; import ContextMenu from "./ContextMenu";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
uid: number; uid: number;
@@ -38,13 +39,9 @@ const User: FC<Props> = ({
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu(); const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
const { curr, loginUid, showStatus } = useAppSelector((store) => { const curr = useAppSelector((store) => store.users.byId[uid], shallowEqual);
return { const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
curr: store.users.byId[uid], const showStatus = useAppSelector((store) => store.server.show_user_online_status, shallowEqual);
loginUid: store.authData.user?.uid,
showStatus: store.server.show_user_online_status
};
});
const handleDoubleClick = () => { const handleDoubleClick = () => {
navigate(`/chat/dm/${uid}`); navigate(`/chat/dm/${uid}`);
}; };
+2 -1
View File
@@ -6,10 +6,11 @@ import dayjs from "dayjs";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { unregister } from "../serviceWorkerRegistration"; import { unregister } from "../serviceWorkerRegistration";
import Button from "./styled/Button"; import Button from "./styled/Button";
import { shallowEqual } from "react-redux";
type Props = {}; type Props = {};
const Version: FC<Props> = () => { const Version: FC<Props> = () => {
const serverVersion = useAppSelector((store) => store.server.version); const serverVersion = useAppSelector((store) => store.server.version, shallowEqual);
const [syncing, setSyncing] = useState(false); const [syncing, setSyncing] = useState(false);
const { t } = useTranslation("setting", { keyPrefix: "version" }); const { t } = useTranslation("setting", { keyPrefix: "version" });
const ts = (process.env.REACT_APP_BUILD_TIME ?? 0) as number; const ts = (process.env.REACT_APP_BUILD_TIME ?? 0) as number;
+7 -12
View File
@@ -1,6 +1,6 @@
// import React from 'react'; // import React from 'react';
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
// import { useTranslation } from 'react-i18next'; // import { useTranslation } from 'react-i18next';
import { Waveform } from "@uiball/loaders"; import { Waveform } from "@uiball/loaders";
@@ -27,17 +27,12 @@ const DMCalling = ({ from, to = 0 }: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { leave, joinVoice, joining } = useVoice({ id: to, context: "dm" }); const { leave, joinVoice, joining } = useVoice({ id: to, context: "dm" });
const containerRef = useRef(null); const containerRef = useRef(null);
const { calling, voicingMembers, fromUser, toUser, loginUser } = useAppSelector((store) => { const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
return { const calling = useAppSelector((store) => store.voice.calling, shallowEqual);
calling: store.voice.calling, const voicingMembers = useAppSelector((store) => store.voice.voicingMembers, shallowEqual);
// voicingInfo: store.voice.voicing ?? {}, const fromUser = useAppSelector((store) => store.users.byId[from], shallowEqual);
voicingMembers: store.voice.voicingMembers, const toUser = useAppSelector((store) => store.users.byId[to], shallowEqual);
fromUser: store.users.byId[from], const sendByMe = loginUid !== toUser.uid;
toUser: store.users.byId[to],
loginUser: store.authData.user
};
});
const sendByMe = loginUser?.uid !== toUser.uid;
useEffect(() => { useEffect(() => {
const ids = voicingMembers.ids; const ids = voicingMembers.ids;
+3 -3
View File
@@ -1,6 +1,6 @@
import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from "react"; import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import Tippy from "@tippyjs/react"; import Tippy from "@tippyjs/react";
import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from "agora-rtc-sdk-ng"; import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from "agora-rtc-sdk-ng";
import clsx from "clsx"; import clsx from "clsx";
@@ -44,7 +44,7 @@ const DeviceList = ({
selected: string; selected: string;
}[]; }[];
}) => { }) => {
const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0); const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
const dispatch = useDispatch(); const dispatch = useDispatch();
// const { t } = useTranslation("chat"); // const { t } = useTranslation("chat");
const toggleVisible = (evt: MouseEvent<HTMLDivElement>) => { const toggleVisible = (evt: MouseEvent<HTMLDivElement>) => {
@@ -166,7 +166,7 @@ type Props = {
const Operations = ({ id, context, mode = "channel" }: Props) => { const Operations = ({ id, context, mode = "channel" }: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0); const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
const [panelVisible, setPanelVisible] = useState<VisibleType>(""); const [panelVisible, setPanelVisible] = useState<VisibleType>("");
const { const {
+6 -10
View File
@@ -1,5 +1,5 @@
import { memo, useEffect } from "react"; import { memo, useEffect } from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import AgoraRTC from "agora-rtc-sdk-ng"; import AgoraRTC from "agora-rtc-sdk-ng";
import { import {
@@ -26,15 +26,11 @@ window.VIDEO_TRACK_MAP = window.VIDEO_TRACK_MAP ?? {};
const inIframe = isInIframe(); const inIframe = isInIframe();
// let tmpUids: number[] = []; // let tmpUids: number[] = [];
const Voice = () => { const Voice = () => {
const { from, to, voiceList, loginUid, voicingInfo } = useAppSelector((store) => { const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
return { const from = useAppSelector((store) => store.voice.callingFrom, shallowEqual);
voicingInfo: store.voice.voicing, const to = useAppSelector((store) => store.voice.callingTo, shallowEqual);
voiceList: store.voice.list, const voiceList = useAppSelector((store) => store.voice.list, shallowEqual);
from: store.voice.callingFrom, const voicingInfo = useAppSelector((store) => store.voice.voicing, shallowEqual);
to: store.voice.callingTo,
loginUid: store.authData.user?.uid
};
});
const { data: enabled } = useGetAgoraStatusQuery(); const { data: enabled } = useGetAgoraStatusQuery();
const [getUsersByChannel] = useLazyGetAgoraUsersByChannelQuery(); const [getUsersByChannel] = useLazyGetAgoraUsersByChannelQuery();
useGetAgoraChannelsQuery( useGetAgoraChannelsQuery(
+31 -24
View File
@@ -1,4 +1,4 @@
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from "agora-rtc-sdk-ng"; import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from "agora-rtc-sdk-ng";
import AudioJoin from "@/assets/join.wav"; import AudioJoin from "@/assets/join.wav";
@@ -22,32 +22,39 @@ type VoiceProps = {
const audioJoin = new Audio(AudioJoin); const audioJoin = new Audio(AudioJoin);
const useVoice = ({ id, context = "channel" }: VoiceProps) => { const useVoice = ({ id, context = "channel" }: VoiceProps) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
voicingInfo, const voicingInfo = useAppSelector((store) => store.voice.voicing, shallowEqual);
loginUid, const fullscreen = useAppSelector(
audioInputDevices, (store) =>
audioOutputDevices,
videoInputDevices,
audioInputDeviceId,
audioOutputDeviceId,
videoInputDeviceId,
fullscreen
} = useAppSelector((store) => {
return {
fullscreen:
context == "channel" context == "channel"
? store.footprint.channelAsides[id] == "voice_fullscreen" ? store.footprint.channelAsides[id] == "voice_fullscreen"
: store.footprint.dmAsides[id] == "voice_fullscreen", : store.footprint.dmAsides[id] == "voice_fullscreen",
loginUid: store.authData.user?.uid ?? 0, shallowEqual
voicingInfo: store.voice.voicing, );
audioInputDevices: store.voice.devices.filter((d) => d.kind == "audioinput") ?? [], const audioInputDevices = useAppSelector(
audioOutputDevices: store.voice.devices.filter((d) => d.kind == "audiooutput") ?? [], (store) => store.voice.devices.filter((d) => d.kind == "audioinput") ?? [],
videoInputDevices: store.voice.devices.filter((d) => d.kind == "videoinput") ?? [], shallowEqual
audioInputDeviceId: store.voice.audioInputDeviceId, );
audioOutputDeviceId: store.voice.audioOutputDeviceId, const audioOutputDevices = useAppSelector(
videoInputDeviceId: store.voice.videoInputDeviceId (store) => store.voice.devices.filter((d) => d.kind == "audiooutput") ?? [],
}; shallowEqual
}); );
const videoInputDevices = useAppSelector(
(store) => store.voice.devices.filter((d) => d.kind == "videoinput") ?? [],
shallowEqual
);
const audioInputDeviceId = useAppSelector(
(store) => store.voice.audioInputDeviceId,
shallowEqual
);
const audioOutputDeviceId = useAppSelector(
(store) => store.voice.audioOutputDeviceId,
shallowEqual
);
const videoInputDeviceId = useAppSelector(
(store) => store.voice.videoInputDeviceId,
shallowEqual
);
const [generateToken] = useGenerateAgoraTokenMutation(); const [generateToken] = useGenerateAgoraTokenMutation();
// const [joining, setJoining] = useState(false); // const [joining, setJoining] = useState(false);
const joinVoice = async () => { const joinVoice = async () => {
+4 -7
View File
@@ -3,16 +3,13 @@ import { useState } from "react";
import { useLazyDeleteMessageQuery, useLazyDeleteMessagesQuery } from "@/app/services/message"; import { useLazyDeleteMessageQuery, useLazyDeleteMessagesQuery } from "@/app/services/message";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { compareVersion } from "@/utils"; import { compareVersion } from "@/utils";
import { shallowEqual } from "react-redux";
export default function useDeleteMessage() { export default function useDeleteMessage() {
const [deleting, setDeleting] = useState(false); const [deleting, setDeleting] = useState(false);
const { loginUser, messageData, serverVersion } = useAppSelector((store) => { const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
return { const serverVersion = useAppSelector((store) => store.server.version, shallowEqual);
serverVersion: store.server.version, const messageData = useAppSelector((store) => store.message, shallowEqual);
messageData: store.message,
loginUser: store.authData.user
};
});
const [batchRemove] = useLazyDeleteMessagesQuery(); const [batchRemove] = useLazyDeleteMessagesQuery();
const [ const [
remove remove
+3 -6
View File
@@ -1,16 +1,13 @@
import { updateDraftMarkdown, updateDraftMixedText } from "@/app/slices/ui"; import { updateDraftMarkdown, updateDraftMixedText } from "@/app/slices/ui";
import { useAppDispatch, useAppSelector } from "@/app/store"; import { useAppDispatch, useAppSelector } from "@/app/store";
import { ChatContext } from "@/types/common"; import { ChatContext } from "@/types/common";
import { shallowEqual } from "react-redux";
const useDraft = ({ context = "dm", id = 0 }: { context: ChatContext; id: number }) => { const useDraft = ({ context = "dm", id = 0 }: { context: ChatContext; id: number }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const _key = `${context}_${id}`; const _key = `${context}_${id}`;
const { draftMarkdown, draftMixedText } = useAppSelector((store) => { const draftMarkdown = useAppSelector((store) => store.ui.draftMarkdown, shallowEqual);
return { const draftMixedText = useAppSelector((store) => store.ui.draftMixedText, shallowEqual);
draftMarkdown: store.ui.draftMarkdown,
draftMixedText: store.ui.draftMixedText
};
});
const getUpdateDraft = (type = "mixed") => { const getUpdateDraft = (type = "mixed") => {
const update = type == "mixed" ? updateDraftMixedText : updateDraftMarkdown; const update = type == "mixed" ? updateDraftMixedText : updateDraftMarkdown;
+2 -3
View File
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
import { useFavoriteMessageMutation, useLazyRemoveFavoriteQuery } from "@/app/services/message"; import { useFavoriteMessageMutation, useLazyRemoveFavoriteQuery } from "@/app/services/message";
import { Favorite } from "@/app/slices/favorites"; import { Favorite } from "@/app/slices/favorites";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
export default function useFavMessage({ export default function useFavMessage({
cid = null, cid = null,
@@ -14,9 +15,7 @@ export default function useFavMessage({
const [removeFav] = useLazyRemoveFavoriteQuery(); const [removeFav] = useLazyRemoveFavoriteQuery();
const [addFav] = useFavoriteMessageMutation(); const [addFav] = useFavoriteMessageMutation();
const [favorites, setFavorites] = useState<Favorite[]>([]); const [favorites, setFavorites] = useState<Favorite[]>([]);
const { favs = [] } = useAppSelector((store) => { const favs = useAppSelector((store) => store.favorites ?? [], shallowEqual);
return { favs: store.favorites };
});
const addFavorite = async (mid: number | number[]) => { const addFavorite = async (mid: number | number[]) => {
const mids = Array.isArray(mid) ? mid.map((i) => +i) : [+mid]; const mids = Array.isArray(mid) ? mid.map((i) => +i) : [+mid];
+2 -1
View File
@@ -2,10 +2,11 @@ import { useEffect, useState } from "react";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { Channel } from "@/types/channel"; import { Channel } from "@/types/channel";
import { shallowEqual } from "react-redux";
export default function useFilteredChannels() { export default function useFilteredChannels() {
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const channels = useAppSelector((store) => Object.values(store.channels.byId)); const channels = useAppSelector((store) => Object.values(store.channels.byId), shallowEqual);
const [filteredChannels, setFilteredChannels] = useState<Channel[]>([]); const [filteredChannels, setFilteredChannels] = useState<Channel[]>([]);
useEffect(() => { useEffect(() => {
+7 -7
View File
@@ -3,16 +3,16 @@ import { escapeRegExp } from "lodash";
import { StoredUser } from "@/app/slices/users"; import { StoredUser } from "@/app/slices/users";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
export default function useFilteredUsers() { export default function useFilteredUsers() {
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const { originUsers, enableContact, isAdmin } = useAppSelector((store) => { const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
return { const originUsers = useAppSelector((store) => Object.values(store.users.byId), shallowEqual);
isAdmin: store.authData.user?.is_admin, const enableContact = useAppSelector(
enableContact: store.server.contact_verification_enable, (store) => store.server.contact_verification_enable,
originUsers: Object.values(store.users.byId) shallowEqual
}; );
});
const [filteredUsers, setFilteredUsers] = useState<StoredUser[]>([]); const [filteredUsers, setFilteredUsers] = useState<StoredUser[]>([]);
const useContactList = enableContact && !isAdmin; const useContactList = enableContact && !isAdmin;
const users = useContactList ? originUsers.filter((u) => u.status == "added") : originUsers; const users = useContactList ? originUsers.filter((u) => u.status == "added") : originUsers;
+2 -1
View File
@@ -8,6 +8,7 @@ import {
import { useGetSMTPStatusQuery } from "@/app/services/server"; import { useGetSMTPStatusQuery } from "@/app/services/server";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import useCopy from "./useCopy"; import useCopy from "./useCopy";
import { shallowEqual } from "react-redux";
const defaultExpire = getInviteLinkExpireList()[4].value; const defaultExpire = getInviteLinkExpireList()[4].value;
const defaultTimes = getInviteLinkTimesList()[0].value; const defaultTimes = getInviteLinkTimesList()[0].value;
@@ -15,7 +16,7 @@ type ParamsProps = { expire: number; times: number };
const defaultParams: ParamsProps = { expire: defaultExpire, times: defaultTimes }; const defaultParams: ParamsProps = { expire: defaultExpire, times: defaultTimes };
export default function useInviteLink(cid?: number) { export default function useInviteLink(cid?: number) {
const [finalLink, setFinalLink] = useState(""); const [finalLink, setFinalLink] = useState("");
const channel = useAppSelector((store) => store.channels.byId[cid ?? 0]); const channel = useAppSelector((store) => store.channels.byId[cid ?? 0], shallowEqual);
const { data: SMTPEnabled, isSuccess: smtpStatusFetchSuccess } = useGetSMTPStatusQuery(); const { data: SMTPEnabled, isSuccess: smtpStatusFetchSuccess } = useGetSMTPStatusQuery();
const [generateInviteLink, { data: channelInviteLink, isLoading: generatingChannelLink }] = const [generateInviteLink, { data: channelInviteLink, isLoading: generatingChannelLink }] =
useLazyCreateInviteLinkQuery(); useLazyCreateInviteLinkQuery();
+3 -3
View File
@@ -1,10 +1,10 @@
import { useLazyLeaveChannelQuery, useUpdateChannelMutation } from "@/app/services/channel"; import { useLazyLeaveChannelQuery, useUpdateChannelMutation } from "@/app/services/channel";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
export default function useLeaveChannel(cid: number) { export default function useLeaveChannel(cid: number) {
const { channel, loginUid } = useAppSelector((store) => { const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
return { channel: store.channels.byId[cid], loginUid: store.authData.user?.uid }; const channel = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
});
const [update, { isLoading: transferring, isSuccess: transferSuccess }] = const [update, { isLoading: transferring, isSuccess: transferSuccess }] =
useUpdateChannelMutation(); useUpdateChannelMutation();
const [leave, { isLoading: leaving, isSuccess: leaveSuccess }] = useLazyLeaveChannelQuery(); const [leave, { isLoading: leaving, isSuccess: leaveSuccess }] = useLazyLeaveChannelQuery();
+4 -7
View File
@@ -6,18 +6,15 @@ import {
useUpsertLicenseMutation useUpsertLicenseMutation
} from "@/app/services/server"; } from "@/app/services/server";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux";
// type Props = { // type Props = {
// refetchOnMountOrArgChange?: boolean // refetchOnMountOrArgChange?: boolean
// } | undefined // } | undefined
const useLicense = (refetchOnMountOrArgChange = false) => { const useLicense = (refetchOnMountOrArgChange = false) => {
const { userCount, isGuest, upgraded } = useAppSelector((store) => { const userCount = useAppSelector((store) => store.users.ids.length, shallowEqual);
return { const upgraded = useAppSelector((store) => store.server.upgraded, shallowEqual);
userCount: store.users.ids.length, const isGuest = useAppSelector((store) => store.authData.guest, shallowEqual);
isGuest: store.authData.guest,
upgraded: store.server.upgraded
};
});
const { const {
data: license, data: license,
refetch: refetchLicense, refetch: refetchLicense,
+2 -1
View File
@@ -4,9 +4,10 @@ import { useLazyGetArchiveMessageQuery } from "@/app/services/message";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { ArchiveMessage } from "@/types/resource"; import { ArchiveMessage } from "@/types/resource";
import { normalizeArchiveData } from "../utils"; import { normalizeArchiveData } from "../utils";
import { shallowEqual } from "react-redux";
export default function useNormalizeMessage(filePath: string | null) { export default function useNormalizeMessage(filePath: string | null) {
const archiveData = useAppSelector((store) => store.archiveMessage[filePath ?? ""]); const archiveData = useAppSelector((store) => store.archiveMessage[filePath ?? ""], shallowEqual);
const [normalizedMessages, setNormalizedMessages] = useState<ArchiveMessage[] | null>(null); const [normalizedMessages, setNormalizedMessages] = useState<ArchiveMessage[] | null>(null);
const [getArchiveMessage, { isError, isLoading, isSuccess }] = useLazyGetArchiveMessageQuery(); const [getArchiveMessage, { isError, isLoading, isSuccess }] = useLazyGetArchiveMessageQuery();
useEffect(() => { useEffect(() => {
+3 -6
View File
@@ -3,15 +3,12 @@ import { useEffect, useState } from "react";
import { usePinMessageMutation, useUnpinMessageMutation } from "@/app/services/message"; import { usePinMessageMutation, useUnpinMessageMutation } from "@/app/services/message";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { PinnedMessage } from "@/types/channel"; import { PinnedMessage } from "@/types/channel";
import { shallowEqual } from "react-redux";
export default function usePinMessage(cid: number) { export default function usePinMessage(cid: number) {
const [pins, setPins] = useState<PinnedMessage[]>([]); const [pins, setPins] = useState<PinnedMessage[]>([]);
const { channel, loginUser } = useAppSelector((store) => { const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
return { const channel = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
channel: store.channels.byId[cid],
loginUser: store.authData.user
};
});
const [pin, { isError, isLoading, isSuccess }] = usePinMessageMutation(); const [pin, { isError, isLoading, isSuccess }] = usePinMessageMutation();
const [unpin, { isError: isUnpinError, isLoading: isUnpinning, isSuccess: isUnpinSuccess }] = const [unpin, { isError: isUnpinError, isLoading: isUnpinning, isSuccess: isUnpinSuccess }] =
useUnpinMessageMutation(); useUnpinMessageMutation();
+15 -21
View File
@@ -7,33 +7,27 @@ import { useLazyGetContactsQuery, useLazyGetUsersQuery } from "@/app/services/us
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import useLicense from "./useLicense"; import useLicense from "./useLicense";
import useStreaming from "./useStreaming"; import useStreaming from "./useStreaming";
import { shallowEqual } from "react-redux";
let preloadChannelMsgs = false; let preloadChannelMsgs = false;
export default function usePreload() { export default function usePreload() {
const { isLoading: loadingLicense } = useLicense(false); const { isLoading: loadingLicense } = useLicense(false);
const [preloadChannelMessages] = useLazyLoadMoreMessagesQuery(); const [preloadChannelMessages] = useLazyLoadMoreMessagesQuery();
const { rehydrate, rehydrated } = useRehydrate(); const { rehydrate, rehydrated } = useRehydrate();
const { const ready = useAppSelector((store) => store.ui.ready, shallowEqual);
ready, const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
loginUid, const enableContacts = useAppSelector(
token, (store) => store.server.contact_verification_enable,
isGuest, shallowEqual
expireTime = +new Date(), );
channelMessageData, const expireTime = useAppSelector(
channelIds, (store) => store.authData.expireTime ?? +new Date(),
enableContacts shallowEqual
} = useAppSelector((store) => { );
return { const channelIds = useAppSelector((store) => store.channels.ids, shallowEqual);
ready: store.ui.ready, const token = useAppSelector((store) => store.authData.token, shallowEqual);
channelIds: store.channels.ids, const isGuest = useAppSelector((store) => store.authData.guest, shallowEqual);
channelMessageData: store.channelMessage, const channelMessageData = useAppSelector((store) => store.channelMessage, shallowEqual);
loginUid: store.authData.user?.uid,
isGuest: store.authData.guest,
token: store.authData.token,
expireTime: store.authData.expireTime,
enableContacts: store.server.contact_verification_enable
};
});
const { startStreaming } = useStreaming(); const { startStreaming } = useStreaming();
const [ const [
getFavorites, getFavorites,
+5 -1
View File
@@ -7,6 +7,7 @@ import { addReplyingMessage, MessagePayload, removeReplyingMessage } from "@/app
import { useAppDispatch, useAppSelector } from "@/app/store"; import { useAppDispatch, useAppSelector } from "@/app/store";
import { ChatContext } from "@/types/common"; import { ChatContext } from "@/types/common";
import { ContentTypeKey } from "@/types/message"; import { ContentTypeKey } from "@/types/message";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
context: ChatContext; context: ChatContext;
@@ -27,7 +28,10 @@ type SendMessageDTO = { type: ContentTypeKey } & Partial<MessagePayload> & {
const useSendMessage = (props?: Props) => { const useSendMessage = (props?: Props) => {
const { context = "dm", from = 0, to = 0 } = props || {}; const { context = "dm", from = 0, to = 0 } = props || {};
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const stageFiles = useAppSelector((store) => store.ui.uploadFiles[`${context}_${to}`] || []); const stageFiles = useAppSelector(
(store) => store.ui.uploadFiles[`${context}_${to}`] || [],
shallowEqual
);
const [replyMessage, { isError: replyErr, isLoading: replying, isSuccess: replySuccess }] = const [replyMessage, { isError: replyErr, isLoading: replying, isSuccess: replySuccess }] =
useReplyMessageMutation(); useReplyMessageMutation();
const [ const [
+7 -4
View File
@@ -45,6 +45,7 @@ import {
} from "@/types/sse"; } from "@/types/sse";
import { getLocalAuthData } from "@/utils"; import { getLocalAuthData } from "@/utils";
import chatMessageHandler from "./chat.handler"; import chatMessageHandler from "./chat.handler";
import { shallowEqual } from "react-redux";
const getQueryString = (params: { [key: string]: string }) => { const getQueryString = (params: { [key: string]: string }) => {
const sp = new URLSearchParams(); const sp = new URLSearchParams();
@@ -62,10 +63,12 @@ let aliveInter: number | ReturnType<typeof setTimeout> = 0;
export default function useStreaming() { export default function useStreaming() {
const [renewToken] = useRenewMutation(); const [renewToken] = useRenewMutation();
const { const user = useAppSelector((store) => store.authData.user, shallowEqual);
authData: { user, guest }, const guest = useAppSelector((store) => store.authData.guest, shallowEqual);
footprint: { afterMid, usersVersion, readUsers, readChannels } const afterMid = useAppSelector((store) => store.footprint.afterMid, shallowEqual);
} = useAppSelector((store) => store); const usersVersion = useAppSelector((store) => store.footprint.usersVersion, shallowEqual);
const readUsers = useAppSelector((store) => store.footprint.readUsers, shallowEqual);
const readChannels = useAppSelector((store) => store.footprint.readChannels, shallowEqual);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const loginUid = user?.uid || 0; const loginUid = user?.uid || 0;
+9 -6
View File
@@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from "@/app/store";
import { Message } from "@/types/channel"; import { Message } from "@/types/channel";
import { ChatContext } from "@/types/common"; import { ChatContext } from "@/types/common";
import { UploadFileResponse } from "@/types/message"; import { UploadFileResponse } from "@/types/message";
import { shallowEqual } from "react-redux";
export type UploadFileData = { export type UploadFileData = {
name: string; name: string;
@@ -35,12 +36,14 @@ const convertHeic2Jpg = async (file: { name: string; type: string; size: number;
const useUploadFile = (props?: IProps) => { const useUploadFile = (props?: IProps) => {
const { context, id } = props ? props : { context: "channel", id: 0 }; const { context, id } = props ? props : { context: "channel", id: 0 };
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { stageFiles, replying } = useAppSelector((store) => { const stageFiles = useAppSelector(
return { (store) => store.ui.uploadFiles[`${context}_${id}`] || [],
stageFiles: store.ui.uploadFiles[`${context}_${id}`] || [], shallowEqual
replying: store.message.replying[`${context}_${id}`] );
}; const replying = useAppSelector(
}); (store) => store.message.replying[`${context}_${id}`],
shallowEqual
);
const [data, setData] = useState<Message | null>(null); const [data, setData] = useState<Message | null>(null);
const canceledRef = useRef(false); const canceledRef = useRef(false);
const sliceUploadedCountRef = useRef(0); const sliceUploadedCountRef = useRef(0);
+10 -8
View File
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
// import { ContentTypes } from "@/app/config"; // import { ContentTypes } from "@/app/config";
import { useMatch, useNavigate } from "react-router-dom"; import { useMatch, useNavigate } from "react-router-dom";
import { hideAll } from "tippy.js"; import { hideAll } from "tippy.js";
@@ -39,13 +39,15 @@ const useUserOperation = ({ uid, cid }: IProps) => {
const [removeInChannel, { isSuccess: removeSuccess }] = useRemoveMembersMutation(); const [removeInChannel, { isSuccess: removeSuccess }] = useRemoveMembersMutation();
const navigateTo = useNavigate(); const navigateTo = useNavigate();
const { copy } = useCopy(); const { copy } = useCopy();
const { user, channel, loginUser } = useAppSelector((store) => { const user = useAppSelector(
return { (store) => (typeof uid !== "undefined" ? store.users.byId[uid] : undefined),
user: typeof uid !== "undefined" ? store.users.byId[uid] : uid, shallowEqual
channel: typeof cid !== "undefined" ? store.channels.byId[cid] : cid, );
loginUser: store.authData.user const channel = useAppSelector(
}; (store) => (typeof cid !== "undefined" ? store.channels.byId[cid] : undefined),
}); shallowEqual
);
const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
useEffect(() => { useEffect(() => {
setPassedUid(uid ?? loginUser?.uid); setPassedUid(uid ?? loginUser?.uid);
+5 -10
View File
@@ -1,6 +1,6 @@
import { memo, useEffect } from "react"; import { memo, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import Tippy from "@tippyjs/react"; import Tippy from "@tippyjs/react";
@@ -29,15 +29,10 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
const { pathname } = useLocation(); const { pathname } = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
const { userIds, data, loginUser, visibleAside } = useAppSelector((store) => { const visibleAside = useAppSelector((store) => store.footprint.channelAsides[cid], shallowEqual);
return { const userIds = useAppSelector((store) => store.users.ids, shallowEqual);
loginUser: store.authData.user, const data = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
userIds: store.users.ids,
data: store.channels.byId[cid],
visibleAside: store.footprint.channelAsides[cid]
};
});
useEffect(() => { useEffect(() => {
if (!data) { if (!data) {
// channel不存在了 回首页 // channel不存在了 回首页
+2 -5
View File
@@ -10,6 +10,7 @@ import FavIcon from "@/assets/icons/bookmark.svg";
import FavList from "../FavList"; import FavList from "../FavList";
import Layout from "../Layout"; import Layout from "../Layout";
import VoiceChat from "../VoiceChat"; import VoiceChat from "../VoiceChat";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
uid: number; uid: number;
@@ -17,11 +18,7 @@ type Props = {
}; };
const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => { const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { currUser } = useAppSelector((store) => { const currUser = useAppSelector((store) => store.users.byId[uid], shallowEqual);
return {
currUser: store.users.byId[uid]
};
});
useEffect(() => { useEffect(() => {
if (!currUser) { if (!currUser) {
// user不存在了 回首页 // user不存在了 回首页
+2 -2
View File
@@ -1,6 +1,6 @@
// import React from "react"; // import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { BASE_ORIGIN } from "@/app/config"; import { BASE_ORIGIN } from "@/app/config";
@@ -17,7 +17,7 @@ const GuestBlankPlaceholder = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { clearLocalData } = useLogout(); const { clearLocalData } = useLogout();
const navigateTo = useNavigate(); const navigateTo = useNavigate();
const serverName = useAppSelector((store) => store.server.name); const serverName = useAppSelector((store) => store.server.name, shallowEqual);
const handleSignIn = () => { const handleSignIn = () => {
dispatch(resetAuthData()); dispatch(resetAuthData());
clearLocalData(); clearLocalData();
+2 -5
View File
@@ -5,17 +5,14 @@ import { useAppSelector } from "@/app/store";
import ChannelIcon from "@/components/ChannelIcon"; import ChannelIcon from "@/components/ChannelIcon";
import GoBackNav from "@/components/GoBackNav"; import GoBackNav from "@/components/GoBackNav";
import Layout from "../Layout"; import Layout from "../Layout";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
cid?: number; cid?: number;
}; };
export default function GuestChannelChat({ cid = 0 }: Props) { export default function GuestChannelChat({ cid = 0 }: Props) {
// const { t } = useTranslation("chat"); // const { t } = useTranslation("chat");
const { data } = useAppSelector((store) => { const data = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
return {
data: store.channels.byId[cid]
};
});
if (!data) return null; if (!data) return null;
const { name, description, is_public } = data; const { name, description, is_public } = data;
return ( return (
+4 -7
View File
@@ -7,6 +7,7 @@ import { useAppSelector } from "@/app/store";
import Avatar from "@/components/Avatar"; import Avatar from "@/components/Avatar";
import { fromNowTime } from "@/utils"; import { fromNowTime } from "@/utils";
import { renderPreviewMessage } from "../../chat/utils"; import { renderPreviewMessage } from "../../chat/utils";
import { shallowEqual } from "react-redux";
interface IProps { interface IProps {
id: number; id: number;
@@ -19,13 +20,9 @@ const Session: FC<IProps> = ({ id, mid }) => {
mid: number; mid: number;
is_public: boolean; is_public: boolean;
}>(); }>();
const { messageData, userData, channelData } = useAppSelector((store) => { const messageData = useAppSelector((store) => store.message, shallowEqual);
return { const userData = useAppSelector((store) => store.users.byId, shallowEqual);
messageData: store.message, const channelData = useAppSelector((store) => store.channels.byId, shallowEqual);
userData: store.users.byId,
channelData: store.channels.byId
};
});
useEffect(() => { useEffect(() => {
const tmp = channelData[id]; const tmp = channelData[id];
+7 -11
View File
@@ -3,6 +3,7 @@ import { FC, useEffect, useState } from "react";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import LoginTip from "../Layout/LoginTip"; import LoginTip from "../Layout/LoginTip";
import Session from "./Session"; import Session from "./Session";
import { shallowEqual } from "react-redux";
export interface ChatSession { export interface ChatSession {
key: string; key: string;
@@ -12,17 +13,12 @@ export interface ChatSession {
type Props = {}; type Props = {};
const SessionList: FC<Props> = () => { const SessionList: FC<Props> = () => {
const [sessions, setSessions] = useState<ChatSession[]>([]); const [sessions, setSessions] = useState<ChatSession[]>([]);
const { channelIDs, readChannels, readUsers, channelMessage, userMessage, loginUid } = const readChannels = useAppSelector((store) => store.footprint.readChannels, shallowEqual);
useAppSelector((store) => { const readUsers = useAppSelector((store) => store.footprint.readUsers, shallowEqual);
return { const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
loginUid: store.authData.user?.uid, const channelIDs = useAppSelector((store) => store.channels.ids, shallowEqual);
channelIDs: store.channels.ids, const channelMessage = useAppSelector((store) => store.channelMessage, shallowEqual);
userMessage: store.userMessage.byId, const userMessage = useAppSelector((store) => store.userMessage.byId, shallowEqual);
channelMessage: store.channelMessage,
readChannels: store.footprint.readChannels,
readUsers: store.footprint.readUsers
};
});
useEffect(() => { useEffect(() => {
const cSessions = channelIDs.map((id) => { const cSessions = channelIDs.map((id) => {
+6 -6
View File
@@ -5,6 +5,7 @@ import IconBlock from "@/assets/icons/block.svg";
import { useUpdateContactStatusMutation } from "../../../app/services/user"; import { useUpdateContactStatusMutation } from "../../../app/services/user";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import { ContactAction } from "../../../types/user"; import { ContactAction } from "../../../types/user";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
uid: number; uid: number;
@@ -13,12 +14,11 @@ type Props = {
const AddContactTip = (props: Props) => { const AddContactTip = (props: Props) => {
const { t } = useTranslation("chat"); const { t } = useTranslation("chat");
const [updateContactStatus] = useUpdateContactStatusMutation(); const [updateContactStatus] = useUpdateContactStatusMutation();
const { targetUser, enableContact } = useAppSelector((store) => { const enableContact = useAppSelector(
return { (store) => store.server.contact_verification_enable,
targetUser: store.users.byId[props.uid], shallowEqual
enableContact: store.server.contact_verification_enable );
}; const targetUser = useAppSelector((store) => store.users.byId[props.uid], shallowEqual);
});
const handleContactStatus = (action: ContactAction) => { const handleContactStatus = (action: ContactAction) => {
updateContactStatus({ target_uid: props.uid, action }); updateContactStatus({ target_uid: props.uid, action });
}; };
+8 -13
View File
@@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import clsx from "clsx"; import clsx from "clsx";
import { updateCallInfo } from "@/app/slices/voice"; import { updateCallInfo } from "@/app/slices/voice";
@@ -114,17 +114,12 @@ type Props = {
const DMVoice = ({ uid }: Props) => { const DMVoice = ({ uid }: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
// const { t } = useTranslation("chat"); // const { t } = useTranslation("chat");
const { const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
voice: { callingFrom, callingTo, voicingMembers }, const userData = useAppSelector((store) => store.users.byId, shallowEqual);
userData, const { callingFrom, callingTo, voicingMembers } = useAppSelector(
loginUser (store) => store.voice,
} = useAppSelector((store) => { shallowEqual
return { );
voice: store.voice,
userData: store.users.byId,
loginUser: store.authData.user
};
});
const { leave, joinVoice, joining } = useVoice({ id: callingTo, context: "dm" }); const { leave, joinVoice, joining } = useVoice({ id: callingTo, context: "dm" });
useEffect(() => { useEffect(() => {
const ids = voicingMembers.ids; const ids = voicingMembers.ids;
@@ -136,7 +131,7 @@ const DMVoice = ({ uid }: Props) => {
const { name: fromUsername, avatar: fromAvatar } = userData[callingFrom]; const { name: fromUsername, avatar: fromAvatar } = userData[callingFrom];
const { name: toUsername, avatar: toAvatar, uid: toUid } = userData[callingTo]; const { name: toUsername, avatar: toAvatar, uid: toUid } = userData[callingTo];
const sendByMe = loginUser?.uid !== toUid; const sendByMe = loginUid !== toUid;
const onlyToSelf = voicingMembers.ids.length == 1 && voicingMembers.ids[0] == callingTo; const onlyToSelf = voicingMembers.ids.length == 1 && voicingMembers.ids[0] == callingTo;
const handleCancel = () => { const handleCancel = () => {
console.log("cancel"); console.log("cancel");
+11 -10
View File
@@ -6,6 +6,7 @@ import { useAppSelector } from "../../../app/store";
import { ChatContext } from "../../../types/common"; import { ChatContext } from "../../../types/common";
import getUnreadCount from "../utils"; import getUnreadCount from "../utils";
import { memo } from "react"; import { memo } from "react";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
context: ChatContext; context: ChatContext;
@@ -15,17 +16,17 @@ type Props = {
// linear-gradient(135deg,_#3C8CE7_0%,_#00EAFF_100%) // linear-gradient(135deg,_#3C8CE7_0%,_#00EAFF_100%)
const NewMessageBottomTip = ({ context, id, scrollToBottom }: Props) => { const NewMessageBottomTip = ({ context, id, scrollToBottom }: Props) => {
const { t } = useTranslation("chat"); const { t } = useTranslation("chat");
const { readIndex, mids, messageData, loginUid } = useAppSelector((store) => { const readIndex = useAppSelector(
return { (store) =>
readIndex:
context == "channel" ? store.footprint.readChannels[id] : store.footprint.readUsers[id], context == "channel" ? store.footprint.readChannels[id] : store.footprint.readUsers[id],
mids: context == "dm" ? store.userMessage.byId[id] : store.channelMessage[id], shallowEqual
selects: store.ui.selectMessages[`${context}_${id}`], );
loginUid: store.authData.user?.uid ?? 0, const mids = useAppSelector(
data: context == "channel" ? store.channels.byId[id] : undefined, (store) => (context == "dm" ? store.userMessage.byId[id] : store.channelMessage[id]),
messageData: store.message || {} shallowEqual
}; );
}); const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
const messageData = useAppSelector((store) => store.message ?? {}, shallowEqual);
const { unreads = 0 } = getUnreadCount({ const { unreads = 0 } = getUnreadCount({
mids, mids,
readIndex, readIndex,
+2 -1
View File
@@ -13,6 +13,7 @@ import IconBookmark from "@/assets/icons/bookmark.svg";
import IconClose from "@/assets/icons/close.circle.svg"; import IconClose from "@/assets/icons/close.circle.svg";
import IconDelete from "@/assets/icons/delete.svg"; import IconDelete from "@/assets/icons/delete.svg";
import IconForward from "@/assets/icons/forward.svg"; import IconForward from "@/assets/icons/forward.svg";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
context: ChatContext; context: ChatContext;
@@ -22,7 +23,7 @@ const Operations: FC<Props> = ({ context, id }) => {
const [deleteModalVisible, setDeleteModalVisible] = useState(false); const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const { canDelete } = useDeleteMessage(); const { canDelete } = useDeleteMessage();
const { addFavorite } = useFavMessage({}); const { addFavorite } = useFavMessage({});
const mids = useAppSelector((store) => store.ui.selectMessages[`${context}_${id}`]); const mids = useAppSelector((store) => store.ui.selectMessages[`${context}_${id}`], shallowEqual);
const [forwardModalVisible, setForwardModalVisible] = useState(false); const [forwardModalVisible, setForwardModalVisible] = useState(false);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -5,6 +5,7 @@ import clsx from "clsx";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import EditIcon from "@/assets/icons/edit.svg"; import EditIcon from "@/assets/icons/edit.svg";
import { shallowEqual } from "react-redux";
type ChannelHeaderProps = { type ChannelHeaderProps = {
cid: number; cid: number;
@@ -12,19 +13,15 @@ type ChannelHeaderProps = {
const ChannelHeader = ({ cid }: ChannelHeaderProps) => { const ChannelHeader = ({ cid }: ChannelHeaderProps) => {
const { pathname } = useLocation(); const { pathname } = useLocation();
const { t } = useTranslation("chat"); const { t } = useTranslation("chat");
const { data, loginUser } = useAppSelector((store) => { const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
return { const data = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
loginUser: store.authData.user,
data: store.channels.byId[cid]
};
});
return ( return (
<div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2"> <div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2">
<h2 className="font-bold text-4xl dark:text-white"> <h2 className="font-bold text-4xl dark:text-white">
{t("welcome_channel", { name: data?.name })} {t("welcome_channel", { name: data?.name })}
</h2> </h2>
<p className="text-gray-600 dark:text-gray-300">{t("welcome_desc", { name: data?.name })} </p> <p className="text-gray-600 dark:text-gray-300">{t("welcome_desc", { name: data?.name })} </p>
{loginUser?.is_admin && ( {isAdmin && (
<NavLink <NavLink
to={`/setting/channel/${cid}/overview?f=${pathname}`} to={`/setting/channel/${cid}/overview?f=${pathname}`}
className="flex items-center gap-1 bg-clip-text text-fill-transparent bg-gradient-to-r from-blue-500 to-primary-400 " className="flex items-center gap-1 bg-clip-text text-fill-transparent bg-gradient-to-r from-blue-500 to-primary-400 "
@@ -1,8 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
// import clsx from 'clsx';
// import { useTranslation } from 'react-i18next';
import { useDebounce } from "rooks"; import { useDebounce } from "rooks";
import { useLazyLoadMoreMessagesQuery, useReadMessageMutation } from "@/app/services/message"; import { useLazyLoadMoreMessagesQuery, useReadMessageMutation } from "@/app/services/message";
@@ -30,24 +28,26 @@ const VirtualMessageFeed = ({ context, id }: Props) => {
const vList = useRef<VirtuosoHandle | null>(null); const vList = useRef<VirtuosoHandle | null>(null);
const [updateReadIndex] = useReadMessageMutation(); const [updateReadIndex] = useReadMessageMutation();
const updateReadDebounced = useDebounce(updateReadIndex, 300); const updateReadDebounced = useDebounce(updateReadIndex, 300);
const { const historyMid = useAppSelector(
historyMid = "", (store) =>
mids = [], context == "dm"
selects, ? store.footprint.historyUsers[id] ?? ""
messageData, : store.footprint.historyChannels[id] ?? "",
loginUser, shallowEqual
footprint );
} = useAppSelector((store) => { const mids = useAppSelector(
return { (store) =>
historyMid: context == "dm" ? store.userMessage.byId[id] ?? [] : store.channelMessage[id] ?? [],
context == "dm" ? store.footprint.historyUsers[id] : store.footprint.historyChannels[id], shallowEqual
mids: context == "dm" ? store.userMessage.byId[id] : store.channelMessage[id], );
selects: store.ui.selectMessages[`${context}_${id}`], const selects = useAppSelector(
footprint: store.footprint, (store) => store.ui.selectMessages[`${context}_${id}`],
loginUser: store.authData.user, shallowEqual
messageData: store.message || {} );
}; const messageData = useAppSelector((store) => store.message || {}, shallowEqual);
}); const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
const readChannels = useAppSelector((store) => store.footprint.readChannels, shallowEqual);
const readUsers = useAppSelector((store) => store.footprint.readUsers, shallowEqual);
useEffect(() => { useEffect(() => {
if (isSuccess && historyData) { if (isSuccess && historyData) {
@@ -102,7 +102,7 @@ const VirtualMessageFeed = ({ context, id }: Props) => {
const handleBottomStateChange = (bottom: boolean) => { const handleBottomStateChange = (bottom: boolean) => {
setAtBottom(bottom); setAtBottom(bottom);
}; };
const readIndex = context == "channel" ? footprint.readChannels[id] : footprint.readUsers[id]; const readIndex = context == "channel" ? readChannels[id] : readUsers[id];
return ( return (
<> <>
<Virtuoso <Virtuoso
@@ -132,7 +132,7 @@ const VirtualMessageFeed = ({ context, id }: Props) => {
if (!curr) return <div className="w-full h-[1px] invisible"></div>; if (!curr) return <div className="w-full h-[1px] invisible"></div>;
const isFirst = idx == 0; const isFirst = idx == 0;
const prev = isFirst ? null : messageData[mids[idx - 1]]; const prev = isFirst ? null : messageData[mids[idx - 1]];
const read = curr?.from_uid == loginUser?.uid || mid <= readIndex; const read = curr?.from_uid == loginUid || mid <= readIndex;
return renderMessageFragment({ return renderMessageFragment({
selectMode: !!selects, selectMode: !!selects,
updateReadIndex: updateReadDebounced, updateReadIndex: updateReadDebounced,
+8 -9
View File
@@ -19,6 +19,7 @@ import LoginTip from "./LoginTip";
import Operations from "./Operations"; import Operations from "./Operations";
import VirtualMessageFeed from "./VirtualMessageFeed"; import VirtualMessageFeed from "./VirtualMessageFeed";
import { platform } from "@/utils"; import { platform } from "@/utils";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
readonly?: boolean; readonly?: boolean;
@@ -45,15 +46,13 @@ const Layout: FC<Props> = ({
const { reachLimit } = useLicense(); const { reachLimit } = useLicense();
const { addStageFile } = useUploadFile({ context, id: to }); const { addStageFile } = useUploadFile({ context, id: to });
const messagesContainer = useRef<HTMLDivElement>(null); const messagesContainer = useRef<HTMLDivElement>(null);
const inputMode = useAppSelector((store) => store.ui.inputMode, shallowEqual);
const { selects, channelsData, usersData, inputMode } = useAppSelector((store) => { const selects = useAppSelector(
return { (store) => store.ui.selectMessages[`${context}_${to}`],
inputMode: store.ui.inputMode, shallowEqual
selects: store.ui.selectMessages[`${context}_${to}`], );
channelsData: store.channels.byId, const channelsData = useAppSelector((store) => store.channels.byId, shallowEqual);
usersData: store.users.byId const usersData = useAppSelector((store) => store.users.byId, shallowEqual);
};
});
const [{ isActive }, drop] = useDrop( const [{ isActive }, drop] = useDrop(
() => ({ () => ({
accept: [NativeTypes.FILE], accept: [NativeTypes.FILE],
+6 -9
View File
@@ -15,6 +15,7 @@ import Tooltip from "../../components/Tooltip";
import User from "../../components/User"; import User from "../../components/User";
import { useVoice } from "../../components/Voice"; import { useVoice } from "../../components/Voice";
import { ChatContext } from "../../types/common"; import { ChatContext } from "../../types/common";
import { shallowEqual } from "react-redux";
type Props = { type Props = {
id: number; id: number;
@@ -34,15 +35,11 @@ const RTCWidget = ({ id, context = "channel" }: Props) => {
startShareScreen, startShareScreen,
stopShareScreen stopShareScreen
} = useVoice({ context, id }); } = useVoice({ context, id });
const { callFrom, callTo, loginUser, channelData, userData } = useAppSelector((store) => { const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
return { const callFrom = useAppSelector((store) => store.voice.callingFrom, shallowEqual);
callFrom: store.voice.callingFrom, const callTo = useAppSelector((store) => store.voice.callingTo, shallowEqual);
callTo: store.voice.callingTo, const channelData = useAppSelector((store) => store.channels.byId, shallowEqual);
userData: store.users.byId, const userData = useAppSelector((store) => store.channels.byId, shallowEqual);
channelData: store.channels.byId,
loginUser: store.authData.user
};
});
if (!loginUser || !voicingInfo || joining) return null; if (!loginUser || !voicingInfo || joining) return null;
// const name = voicingInfo.context == "channel" ? channelData[voicingInfo.id]?.name : userData[voicingInfo.id]?.name; // const name = voicingInfo.context == "channel" ? channelData[voicingInfo.id]?.name : userData[voicingInfo.id]?.name;
// if (!name) return null; // if (!name) return null;
+5 -6
View File
@@ -1,6 +1,6 @@
import { FC, ReactElement } from "react"; import { FC, ReactElement } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { useLocation, useMatch, useNavigate } from "react-router-dom"; import { useLocation, useMatch, useNavigate } from "react-router-dom";
import Tippy from "@tippyjs/react"; import Tippy from "@tippyjs/react";
@@ -51,11 +51,10 @@ const SessionContextMenu: FC<Props> = ({
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigateTo = useNavigate(); const navigateTo = useNavigate();
const { pathname } = useLocation(); const { pathname } = useLocation();
const { channelMuted } = useAppSelector((store) => { const channelMuted = useAppSelector(
return { (store) => (context == "channel" ? store.footprint.muteChannels[id] : false),
channelMuted: context == "channel" ? store.footprint.muteChannels[id] : false shallowEqual
}; );
});
const handleChannelSetting = () => { const handleChannelSetting = () => {
navigateTo(`/setting/channel/${id}/overview?f=${pathname}`); navigateTo(`/setting/channel/${id}/overview?f=${pathname}`);
+20 -25
View File
@@ -17,6 +17,7 @@ import IconMute from "@/assets/icons/mute.svg";
import IconVoicing from "@/assets/icons/voicing.svg"; import IconVoicing from "@/assets/icons/voicing.svg";
import getUnreadCount, { renderPreviewMessage } from "../utils"; import getUnreadCount, { renderPreviewMessage } from "../utils";
import ContextMenu from "./ContextMenu"; import ContextMenu from "./ContextMenu";
import { shallowEqual } from "react-redux";
interface IProps { interface IProps {
type?: ChatContext; type?: ChatContext;
@@ -67,31 +68,25 @@ const Session: FC<IProps> = ({
mid: number; mid: number;
is_public: boolean; is_public: boolean;
}>(); }>();
const { const loginUid = useAppSelector((store) => store.authData.user?.uid || 0, shallowEqual);
callingFrom, const callingFrom = useAppSelector((store) => store.voice.callingFrom, shallowEqual);
callingTo, const callingTo = useAppSelector((store) => store.voice.callingTo, shallowEqual);
messageData, const voiceList = useAppSelector((store) => store.voice.list, shallowEqual);
userData, const mids = useAppSelector(
channelData, (store) => (type == "dm" ? store.userMessage.byId[id] : store.channelMessage[id]),
readIndex, shallowEqual
loginUid, );
mids, const muted = useAppSelector(
muted, (store) => (type == "dm" ? store.footprint.muteUsers[id] : store.footprint.muteChannels[id]),
voiceList shallowEqual
} = useAppSelector((store) => { );
return { const readIndex = useAppSelector(
callingFrom: store.voice.callingFrom, (store) => (type == "dm" ? store.footprint.readUsers[id] : store.footprint.readChannels[id]),
callingTo: store.voice.callingTo, shallowEqual
voiceList: store.voice.list, );
mids: type == "dm" ? store.userMessage.byId[id] : store.channelMessage[id], const messageData = useAppSelector((store) => store.message, shallowEqual);
loginUid: store.authData.user?.uid || 0, const userData = useAppSelector((store) => store.users.byId, shallowEqual);
readIndex: type == "dm" ? store.footprint.readUsers[id] : store.footprint.readChannels[id], const channelData = useAppSelector((store) => store.channels.byId, shallowEqual);
messageData: store.message,
userData: store.users.byId,
channelData: store.channels.byId,
muted: type == "dm" ? store.footprint.muteUsers[id] : store.footprint.muteChannels[id]
};
});
useEffect(() => { useEffect(() => {
const tmp = type == "dm" ? userData[id] : channelData[id]; const tmp = type == "dm" ? userData[id] : channelData[id];
+9 -13
View File
@@ -7,6 +7,7 @@ import { PinChatTargetChannel, PinChatTargetUser } from "@/types/sse";
import InviteModal from "@/components/InviteModal"; import InviteModal from "@/components/InviteModal";
import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal"; import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal";
import Session from "./Session"; import Session from "./Session";
import { shallowEqual } from "react-redux";
export interface ChatSession { export interface ChatSession {
type: ChatContext; type: ChatContext;
@@ -23,19 +24,14 @@ const SessionList: FC<Props> = ({ tempSession }) => {
const [inviteChannelId, setInviteChannelId] = useState<number>(); const [inviteChannelId, setInviteChannelId] = useState<number>();
const [sessions, setSessions] = useState<ChatSession[]>([]); const [sessions, setSessions] = useState<ChatSession[]>([]);
const [pinSessions, setPinSessions] = useState<ChatSession[]>([]); const [pinSessions, setPinSessions] = useState<ChatSession[]>([]);
const { pins, channelIDs, DMs, readChannels, readUsers, channelMessage, userMessage, loginUid } = const readChannels = useAppSelector((store) => store.footprint.readChannels, shallowEqual);
useAppSelector((store) => { const readUsers = useAppSelector((store) => store.footprint.readUsers, shallowEqual);
return { const loginUid = useAppSelector((store) => store.authData.user?.uid, shallowEqual);
pins: store.footprint.pinChats, const channelIDs = useAppSelector((store) => store.channels.ids, shallowEqual);
loginUid: store.authData.user?.uid, const DMs = useAppSelector((store) => store.userMessage.ids, shallowEqual);
channelIDs: store.channels.ids, const pins = useAppSelector((store) => store.footprint.pinChats, shallowEqual);
DMs: store.userMessage.ids, const userMessage = useAppSelector((store) => store.userMessage.byId, shallowEqual);
userMessage: store.userMessage.byId, const channelMessage = useAppSelector((store) => store.channelMessage, shallowEqual);
channelMessage: store.channelMessage,
readChannels: store.footprint.readChannels,
readUsers: store.footprint.readUsers
};
});
useEffect(() => { useEffect(() => {
// const pinDMs= // const pinDMs=
@@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import clsx from "clsx"; import clsx from "clsx";
import { updateChannelVisibleAside } from "@/app/slices/footprint"; import { updateChannelVisibleAside } from "@/app/slices/footprint";
@@ -21,12 +21,8 @@ type Props = {
const VoiceManagement = ({ id, context, info }: Props) => { const VoiceManagement = ({ id, context, info }: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { userData, voicingMembers } = useAppSelector((store) => { const userMap = useAppSelector((store) => store.users.byId, shallowEqual);
return { const voicingMembers = useAppSelector((store) => store.voice.voicingMembers, shallowEqual);
userData: store.users.byId,
voicingMembers: store.voice.voicingMembers
};
});
useEffect(() => { useEffect(() => {
const ids = voicingMembers.ids; const ids = voicingMembers.ids;
ids.forEach((id) => { ids.forEach((id) => {
@@ -66,7 +62,7 @@ const VoiceManagement = ({ id, context, info }: Props) => {
<div className="w-full h-full py-2 flex flex-col"> <div className="w-full h-full py-2 flex flex-col">
<ul className="flex grow flex-col"> <ul className="flex grow flex-col">
{members.map((uid) => { {members.map((uid) => {
const curr = userData[uid]; const curr = userMap[uid];
if (!curr) return null; if (!curr) return null;
const { muted, speakingVolume = 0 } = membersData[uid]; const { muted, speakingVolume = 0 } = membersData[uid];
const speaking = speakingVolume > 50; const speaking = speakingVolume > 50;
+9 -14
View File
@@ -1,9 +1,6 @@
// import React from 'react';
// import Tippy from '@tippyjs/react';
// import { useState } from 'react';
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { useGetAgoraStatusQuery } from "@/app/services/server"; import { useGetAgoraStatusQuery } from "@/app/services/server";
import { updateChannelVisibleAside, updateDMVisibleAside } from "@/app/slices/footprint"; import { updateChannelVisibleAside, updateDMVisibleAside } from "@/app/slices/footprint";
@@ -23,14 +20,12 @@ const isIframe = isInIframe();
const VoiceChat = ({ id, context = "channel" }: Props) => { const VoiceChat = ({ id, context = "channel" }: Props) => {
const { joinVoice, joined, joining = false, joinedAtThisContext } = useVoice({ id, context }); const { joinVoice, joined, joining = false, joinedAtThisContext } = useVoice({ id, context });
const dispatch = useDispatch(); const dispatch = useDispatch();
const { loginUser, voiceList, visibleAside } = useAppSelector((store) => { const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
return { const visibleAside = useAppSelector(
loginUser: store.authData.user, (store) => (context == "channel" ? store.footprint.channelAsides[id] : null),
contextData: context == "channel" ? store.channels.byId[id] : store.users.byId[id], shallowEqual
voiceList: store.voice.list, );
visibleAside: context == "channel" ? store.footprint.channelAsides[id] : null const voiceList = useAppSelector((store) => store.voice.list, shallowEqual);
};
});
const { data: enabled } = useGetAgoraStatusQuery(); const { data: enabled } = useGetAgoraStatusQuery();
const { t } = useTranslation("chat"); const { t } = useTranslation("chat");
const toggleDashboard = () => { const toggleDashboard = () => {
@@ -53,14 +48,14 @@ const VoiceChat = ({ id, context = "channel" }: Props) => {
dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data)); dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data));
// 实时显示calling box // 实时显示calling box
if (!joinedAtThisContext && context == "dm") { if (!joinedAtThisContext && context == "dm") {
dispatch(updateCallInfo({ from: loginUser?.uid ?? 0, to: id, calling: false })); dispatch(updateCallInfo({ from: loginUid, to: id, calling: false }));
} }
}; };
const handleInIframe = () => { const handleInIframe = () => {
// todo // todo
toast.error("Voice is not supported in iframe"); toast.error("Voice is not supported in iframe");
}; };
if (!loginUser || !enabled) return null; if (loginUid == 0 || !enabled) return null;
const visible = visibleAside == "voice"; const visible = visibleAside == "voice";
const memberCount = voiceList.find((v) => v.context == context && v.id == id)?.memberCount ?? 0; const memberCount = voiceList.find((v) => v.context == context && v.id == id)?.memberCount ?? 0;
const badgeClass = `absolute -top-2 -right-2 w-4 h-4 rounded-full bg-primary-400 text-white `; const badgeClass = `absolute -top-2 -right-2 w-4 h-4 rounded-full bg-primary-400 text-white `;
+7 -8
View File
@@ -1,5 +1,5 @@
import { MouseEvent, useEffect, useState } from "react"; import { MouseEvent, useEffect, useState } from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import clsx from "clsx"; import clsx from "clsx";
import Operations from "@/components/Voice/Operations"; import Operations from "@/components/Voice/Operations";
@@ -21,13 +21,12 @@ type Props = {
const VoiceFullscreen = ({ id, context }: Props) => { const VoiceFullscreen = ({ id, context }: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [speakingUid, setSpeakingUid] = useState(0); const [speakingUid, setSpeakingUid] = useState(0);
const { name, userData, voicingMembers } = useAppSelector((store) => { const name = useAppSelector(
return { (store) => (context == "channel" ? store.channels.byId[id].name : store.users.byId[id].name),
userData: store.users.byId, shallowEqual
voicingMembers: store.voice.voicingMembers, );
name: context == "channel" ? store.channels.byId[id].name : store.users.byId[id].name const userData = useAppSelector((store) => store.users.byId, shallowEqual);
}; const voicingMembers = useAppSelector((store) => store.voice.voicingMembers, shallowEqual);
});
useEffect(() => { useEffect(() => {
const ids = voicingMembers.ids; const ids = voicingMembers.ids;
ids.forEach((id) => { ids.forEach((id) => {
+10 -14
View File
@@ -16,6 +16,7 @@ import GuestSessionList from "./GuestSessionList";
import RTCWidget from "./RTCWidget"; import RTCWidget from "./RTCWidget";
import SessionList from "./SessionList"; import SessionList from "./SessionList";
import VoiceFullscreen from "./VoiceFullscreen"; import VoiceFullscreen from "./VoiceFullscreen";
import { shallowEqual } from "react-redux";
function ChatPage() { function ChatPage() {
const isHomePath = useMatch(`/`); const isHomePath = useMatch(`/`);
@@ -24,21 +25,16 @@ function ChatPage() {
const [channelModalVisible, setChannelModalVisible] = useState(false); const [channelModalVisible, setChannelModalVisible] = useState(false);
const [usersModalVisible, setUsersModalVisible] = useState(false); const [usersModalVisible, setUsersModalVisible] = useState(false);
const { channel_id = 0, user_id = 0 } = useParams(); const { channel_id = 0, user_id = 0 } = useParams();
const { const callingTo = useAppSelector((store) => store.voice.callingTo, shallowEqual);
sessionUids, const aside = useAppSelector(
isGuest, (store) =>
aside = "", channel_id
callingTo
} = useAppSelector((store) => {
return {
callingTo: store.voice.callingTo,
isGuest: store.authData.guest,
sessionUids: store.userMessage.ids,
aside: channel_id
? store.footprint.channelAsides[+channel_id] ? store.footprint.channelAsides[+channel_id]
: store.footprint.dmAsides[store.voice.callingTo] : store.footprint.dmAsides[store.voice.callingTo],
}; shallowEqual
}); );
const isGuest = useAppSelector((store) => store.authData.guest, shallowEqual);
const sessionUids = useAppSelector((store) => store.userMessage.ids, shallowEqual);
const toggleUsersModalVisible = () => { const toggleUsersModalVisible = () => {
setUsersModalVisible((prev) => !prev); setUsersModalVisible((prev) => !prev);
}; };
+5 -2
View File
@@ -1,5 +1,5 @@
// import React from "react"; // import React from "react";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ContentTypes } from "@/app/config"; import { ContentTypes } from "@/app/config";
@@ -97,7 +97,10 @@ export const renderPreviewMessage = (message = null) => {
const MessageWrapper = ({ selectMode = false, context, id, mid, divider, children, ...rest }) => { const MessageWrapper = ({ selectMode = false, context, id, mid, divider, children, ...rest }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const selects = useAppSelector((store) => store.ui.selectMessages[`${context}_${id}`]); const selects = useAppSelector(
(store) => store.ui.selectMessages[`${context}_${id}`],
shallowEqual
);
const selected = !!(selects && selects.find((s) => s == mid)); const selected = !!(selects && selects.find((s) => s == mid));
const toggleSelect = () => { const toggleSelect = () => {
const operation = selected ? "remove" : "add"; const operation = selected ? "remove" : "add";
+4 -7
View File
@@ -14,6 +14,7 @@ import IconAudio from "@/assets/icons/file.audio.svg";
import IconImage from "@/assets/icons/file.image.svg"; import IconImage from "@/assets/icons/file.image.svg";
import IconUnknown from "@/assets/icons/file.unknown.svg"; import IconUnknown from "@/assets/icons/file.unknown.svg";
import IconVideo from "@/assets/icons/file.video.svg"; import IconVideo from "@/assets/icons/file.video.svg";
import { shallowEqual } from "react-redux";
type filter = "audio" | "video" | "image" | ""; type filter = "audio" | "video" | "image" | "";
@@ -44,13 +45,9 @@ function FavsPage() {
filter: "audio" filter: "audio"
} }
]; ];
const { favorites, channelData, userData } = useAppSelector((store) => { const favorites = useAppSelector((store) => store.favorites, shallowEqual);
return { const channelData = useAppSelector((store) => store.channels.byId, shallowEqual);
favorites: store.favorites, const userData = useAppSelector((store) => store.users.byId, shallowEqual);
userData: store.users.byId,
channelData: store.channels.byId
};
});
const handleFilter = (ftr: filter) => { const handleFilter = (ftr: filter) => {
setFilter(ftr); setFilter(ftr);
}; };
+3 -3
View File
@@ -10,6 +10,7 @@ import FilterChannel from "./Channel";
import FilterDate, { Dates } from "./Date"; import FilterDate, { Dates } from "./Date";
import FilterFrom from "./From"; import FilterFrom from "./From";
import FilterType, { FileTypes } from "./Type"; import FilterType, { FileTypes } from "./Type";
import { shallowEqual } from "react-redux";
const getClass = (selected: boolean) => { const getClass = (selected: boolean) => {
return clsx( return clsx(
@@ -42,9 +43,8 @@ export default function Filter({ filter, updateFilter }) {
}; };
toggleFilterVisible(tmp); toggleFilterVisible(tmp);
}; };
const { userMap, channelMap } = useAppSelector((store) => { const userMap = useAppSelector((store) => store.users.byId, shallowEqual);
return { userMap: store.users.byId, channelMap: store.channels.byId }; const channelMap = useAppSelector((store) => store.channels.byId, shallowEqual);
});
const { from, channel, type, date } = filter; const { from, channel, type, date } = filter;
const { const {
+2 -5
View File
@@ -9,6 +9,7 @@ import Filter from "./Filter";
import Search from "./Search"; import Search from "./Search";
import View from "./View"; import View from "./View";
import { useLazyGetFilesQuery } from "@/app/services/server"; import { useLazyGetFilesQuery } from "@/app/services/server";
import { shallowEqual } from "react-redux";
const checkFilter = (data, filter, channelMessage) => { const checkFilter = (data, filter, channelMessage) => {
let selected = true; let selected = true;
@@ -35,11 +36,7 @@ function Files() {
const [getFiles, { data }] = useLazyGetFilesQuery(); const [getFiles, { data }] = useLazyGetFilesQuery();
const listContainerRef = useRef<HTMLDivElement>(); const listContainerRef = useRef<HTMLDivElement>();
const [filter, setFilter] = useState({}); const [filter, setFilter] = useState({});
const { view } = useAppSelector((store) => { const view = useAppSelector((store) => store.ui.fileListView.view, shallowEqual);
return {
view: store.ui.fileListView
};
});
const updateFilter = (data) => { const updateFilter = (data) => {
setFilter((prev) => { setFilter((prev) => {
+3 -1
View File
@@ -4,12 +4,14 @@ import { Navigate } from "react-router-dom";
import { useLazyGuestLoginQuery } from "../app/services/auth"; import { useLazyGuestLoginQuery } from "../app/services/auth";
import { useAppSelector } from "../app/store"; import { useAppSelector } from "../app/store";
import { useEffect } from "react"; import { useEffect } from "react";
import { shallowEqual } from "react-redux";
// type Props = {}; // type Props = {};
const GuestLogin = () => { const GuestLogin = () => {
const [guestLogin] = useLazyGuestLoginQuery(); const [guestLogin] = useLazyGuestLoginQuery();
const { token, guest } = useAppSelector((store) => store.authData); const token = useAppSelector((store) => store.authData.token, shallowEqual);
const guest = useAppSelector((store) => store.authData.guest, shallowEqual);
useEffect(() => { useEffect(() => {
if (!guest || !token) { if (!guest || !token) {
guestLogin(); guestLogin();
+5 -11
View File
@@ -6,6 +6,7 @@ import ChatIcon from "@/assets/icons/chat.svg";
import SettingIcon from "@/assets/icons/setting.svg"; import SettingIcon from "@/assets/icons/setting.svg";
import UserIcon from "@/assets/icons/user.svg"; import UserIcon from "@/assets/icons/user.svg";
import { useAppSelector } from "../../app/store"; import { useAppSelector } from "../../app/store";
import { shallowEqual } from "react-redux";
// type Props = {} // type Props = {}
@@ -16,17 +17,10 @@ const MobileNavs = () => {
const isDMChat = useMatch(`/chat/dm/:user_id`); const isDMChat = useMatch(`/chat/dm/:user_id`);
// const isSettingPage = useMatch(`/setting`); // const isSettingPage = useMatch(`/setting`);
const isChannelChat = useMatch(`/chat/channel/:channel_id`); const isChannelChat = useMatch(`/chat/channel/:channel_id`);
const { const { chat: chatPath, user: userPath } = useAppSelector(
ui: { (store) => store.ui.rememberedNavs,
rememberedNavs: { chat: chatPath, user: userPath } shallowEqual
} );
} = useAppSelector((store) => {
return {
ui: store.ui,
loginUid: store.authData.user?.uid,
guest: store.authData.guest
};
});
const linkClass = `flex`; const linkClass = `flex`;
const isChatPage = isHomePath || pathname.startsWith("/chat"); const isChatPage = isHomePath || pathname.startsWith("/chat");
+2 -1
View File
@@ -3,6 +3,7 @@ import { NavLink, useLocation } from "react-router-dom";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import Avatar from "@/components/Avatar"; import Avatar from "@/components/Avatar";
import { shallowEqual } from "react-redux";
interface Props { interface Props {
uid: number; uid: number;
@@ -10,7 +11,7 @@ interface Props {
const User: FC<Props> = ({ uid }) => { const User: FC<Props> = ({ uid }) => {
const { pathname } = useLocation(); const { pathname } = useLocation();
const user = useAppSelector((store) => store.users.byId[uid]); const user = useAppSelector((store) => store.users.byId[uid], shallowEqual);
if (!user) return null; if (!user) return null;
return ( return (
+8 -16
View File
@@ -1,6 +1,6 @@
import { memo, useEffect } from "react"; import { memo, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { NavLink, Outlet, useLocation, useMatch } from "react-router-dom"; import { NavLink, Outlet, useLocation, useMatch } from "react-router-dom";
import { updateRememberedNavs } from "@/app/slices/ui"; import { updateRememberedNavs } from "@/app/slices/ui";
@@ -28,21 +28,13 @@ function HomePage() {
const isHomePath = useMatch(`/`); const isHomePath = useMatch(`/`);
const isChatHomePath = useMatch(`/chat`); const isChatHomePath = useMatch(`/chat`);
const { pathname } = useLocation(); const { pathname } = useLocation();
const { const roleChanged = useAppSelector((store) => store.authData.roleChanged, shallowEqual);
roleChanged, const guest = useAppSelector((store) => store.authData.guest, shallowEqual);
loginUser: { uid: loginUid }, const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
guest, const { chat: chatPath, user: userPath } = useAppSelector(
ui: { (store) => store.ui.rememberedNavs,
rememberedNavs: { chat: chatPath, user: userPath } shallowEqual
} );
} = useAppSelector((store) => {
return {
ui: store.ui,
loginUser: store.authData.user ?? { uid: 0, is_admin: false },
guest: store.authData.guest,
roleChanged: store.authData.roleChanged
};
});
// preload basic data // preload basic data
const { success } = usePreload(); const { success } = usePreload();
useEffect(() => { useEffect(() => {
+6 -9
View File
@@ -1,6 +1,6 @@
import { lazy, useEffect } from "react"; import { lazy, memo, useEffect } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Provider } from "react-redux"; import { Provider, shallowEqual } from "react-redux";
import { HashRouter, Route, Routes } from "react-router-dom"; import { HashRouter, Route, Routes } from "react-router-dom";
import { isEqual } from "lodash"; import { isEqual } from "lodash";
@@ -40,12 +40,8 @@ const HomePage = lazy(() => import("./home"));
let toastId: string; let toastId: string;
const PageRoutes = () => { const PageRoutes = () => {
const { const version = useAppSelector((store) => store.server.version, shallowEqual);
ui: { online }, const online = useAppSelector((store) => store.ui.online, shallowEqual);
version
} = useAppSelector((store) => {
return { ui: store.ui, version: store.server.version };
}, isEqual);
// 提前获取device token // 提前获取device token
useDeviceToken(vapidKey); useDeviceToken(vapidKey);
// 初始化元信息 // 初始化元信息
@@ -296,7 +292,7 @@ const PageRoutes = () => {
); );
}; };
export default function ReduxRoutes() { function ReduxRoutes() {
return ( return (
<Provider store={store}> <Provider store={store}>
{isElectronContext() && <Electron />} {isElectronContext() && <Electron />}
@@ -305,3 +301,4 @@ export default function ReduxRoutes() {
</Provider> </Provider>
); );
} }
export default memo(ReduxRoutes);
+2 -1
View File
@@ -5,10 +5,11 @@ import { useCheckMagicTokenValidMutation } from "../../app/services/auth";
import { useJoinPrivateChannelMutation, useLazyGetChannelQuery } from "../../app/services/channel"; import { useJoinPrivateChannelMutation, useLazyGetChannelQuery } from "../../app/services/channel";
import { useAppSelector } from "../../app/store"; import { useAppSelector } from "../../app/store";
import StyledButton from "../../components/styled/Button"; import StyledButton from "../../components/styled/Button";
import { shallowEqual } from "react-redux";
const InvitePrivate = () => { const InvitePrivate = () => {
const { channel_id } = useParams(); const { channel_id } = useParams();
const server = useAppSelector((store) => store.server); const server = useAppSelector((store) => store.server, shallowEqual);
const navigateTo = useNavigate(); const navigateTo = useNavigate();
const [joinChannel, { isLoading, data, isSuccess }] = useJoinPrivateChannelMutation(); const [joinChannel, { isLoading, data, isSuccess }] = useJoinPrivateChannelMutation();
const [fetchChannelInfo, { data: channel, isSuccess: fetchChannelSuccess }] = const [fetchChannelInfo, { data: channel, isSuccess: fetchChannelSuccess }] =
+2 -1
View File
@@ -16,13 +16,14 @@ import IconBack from "@/assets/icons/arrow.left.svg";
import MagicLinkLogin from "./MagicLinkLogin"; import MagicLinkLogin from "./MagicLinkLogin";
import SignUpLink from "./SignUpLink"; import SignUpLink from "./SignUpLink";
import SocialLoginButtons from "./SocialLoginButtons"; import SocialLoginButtons from "./SocialLoginButtons";
import { shallowEqual } from "react-redux";
const defaultInput = { const defaultInput = {
email: "", email: "",
password: "" password: ""
}; };
export default function LoginPage() { export default function LoginPage() {
const { name: serverName, logo } = useAppSelector((store) => store.server); const { name: serverName, logo } = useAppSelector((store) => store.server, shallowEqual);
const { t } = useTranslation("auth"); const { t } = useTranslation("auth");
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
const { data: enableSMTP, isLoading: loadingSMTPStatus } = useGetSMTPStatusQuery(); const { data: enableSMTP, isLoading: loadingSMTPStatus } = useGetSMTPStatusQuery();
@@ -1,7 +1,7 @@
import { FC, useEffect, useRef, useState } from "react"; import { FC, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { useWizard } from "react-use-wizard"; import { useWizard } from "react-use-wizard";
import { useLoginMutation } from "@/app/services/auth"; import { useLoginMutation } from "@/app/services/auth";
@@ -18,7 +18,7 @@ const AdminAccount: FC<Props> = ({ serverName }) => {
const { t } = useTranslation("welcome", { keyPrefix: "onboarding" }); const { t } = useTranslation("welcome", { keyPrefix: "onboarding" });
const { nextStep } = useWizard(); const { nextStep } = useWizard();
const formRef = useRef<HTMLFormElement | undefined>(); const formRef = useRef<HTMLFormElement | undefined>();
const loggedIn = useAppSelector((store) => !!store.authData.token); const loggedIn = useAppSelector((store) => !!store.authData.token, shallowEqual);
const dispatch = useDispatch(); const dispatch = useDispatch();
const [createAdmin, { isLoading: isSigningUp, isError: signUpError, isSuccess: signUpOk }] = const [createAdmin, { isLoading: isSigningUp, isError: signUpError, isSuccess: signUpOk }] =
useCreateAdminMutation(); useCreateAdminMutation();
+2 -2
View File
@@ -1,7 +1,7 @@
import { ChangeEvent, FormEvent, useEffect, useState } from "react"; import { ChangeEvent, FormEvent, useEffect, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { shallowEqual, useDispatch } from "react-redux";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import BASE_URL, { KEY_LOCAL_MAGIC_TOKEN } from "@/app/config"; import BASE_URL, { KEY_LOCAL_MAGIC_TOKEN } from "@/app/config";
@@ -32,7 +32,7 @@ interface AuthForm {
export default function Register() { export default function Register() {
let [searchParams] = useSearchParams(new URLSearchParams(location.search)); let [searchParams] = useSearchParams(new URLSearchParams(location.search));
const ctx = searchParams.get("ctx"); const ctx = searchParams.get("ctx");
const serverName = useAppSelector((store) => store.server.name); const serverName = useAppSelector((store) => store.server.name, shallowEqual);
const { t } = useTranslation("auth"); const { t } = useTranslation("auth");
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
const [sendRegMagicLink, { isLoading: signingUp, data, isSuccess }] = const [sendRegMagicLink, { isLoading: signingUp, data, isSuccess }] =
+2 -3
View File
@@ -42,9 +42,8 @@ export default function Filter({ filter, updateFilter }) {
}; };
toggleFilterVisible(tmp); toggleFilterVisible(tmp);
}; };
const { userMap, channelMap } = useAppSelector((store) => { const userMap = useAppSelector((store) => store.users.byId, shallowEqual);
return { userMap: store.users.byId, channelMap: store.channels.byId }; const channelMap = useAppSelector((store) => store.channels.byId, shallowEqual);
});
const { from, channel, type, date } = filter; const { from, channel, type, date } = filter;
const { const {
+5 -8
View File
@@ -9,6 +9,7 @@ import useExpiredResMap from "@/hooks/useExpiredResMap";
import Filter from "./Filter"; import Filter from "./Filter";
import Search from "./Search"; import Search from "./Search";
import View from "./View"; import View from "./View";
import { shallowEqual } from "react-redux";
const checkFilter = (data, filter, channelMessage) => { const checkFilter = (data, filter, channelMessage) => {
let selected = true; let selected = true;
@@ -36,14 +37,10 @@ function ResourceManagement() {
const { isExpired } = useExpiredResMap(); const { isExpired } = useExpiredResMap();
const listContainerRef = useRef<HTMLDivElement>(); const listContainerRef = useRef<HTMLDivElement>();
const [filter, setFilter] = useState({}); const [filter, setFilter] = useState({});
const { message, view, channelMessage, fileMsgs } = useAppSelector((store) => { const view = useAppSelector((store) => store.ui.fileListView.view, shallowEqual);
return { const message = useAppSelector((store) => store.message, shallowEqual);
fileMsgs: store.fileMessage, const fileMsgs = useAppSelector((store) => store.fileMessage, shallowEqual);
message: store.message, const channelMessage = useAppSelector((store) => store.channelMessage, shallowEqual);
channelMessage: store.channelMessage,
view: store.ui.fileListView
};
});
const updateFilter = (data) => { const updateFilter = (data) => {
setFilter((prev) => { setFilter((prev) => {
+2 -1
View File
@@ -4,11 +4,12 @@ import { useTranslation } from "react-i18next";
import IconCopy from "@/assets/icons/copy.svg"; import IconCopy from "@/assets/icons/copy.svg";
import { useAppSelector } from "../../app/store"; import { useAppSelector } from "../../app/store";
import useCopy from "../../hooks/useCopy"; import useCopy from "../../hooks/useCopy";
import { shallowEqual } from "react-redux";
// type Props = {} // type Props = {}
const APIUrl = `${location.origin}/api/swagger`; const APIUrl = `${location.origin}/api/swagger`;
const APIDocument = () => { const APIDocument = () => {
const token = useAppSelector((store) => store.authData.token); const token = useAppSelector((store) => store.authData.token, shallowEqual);
const { copy } = useCopy(); const { copy } = useCopy();
const { t } = useTranslation("setting"); const { t } = useTranslation("setting");
const handleCopy = () => { const handleCopy = () => {
+5 -1
View File
@@ -13,6 +13,7 @@ import DeleteModal from "./DeleteModal";
import NameEdit from "./NameEdit"; import NameEdit from "./NameEdit";
import WebhookEdit from "./WebhookEdit"; import WebhookEdit from "./WebhookEdit";
import WebhookModal from "./WebhookModal"; import WebhookModal from "./WebhookModal";
import { shallowEqual } from "react-redux";
type TipProps = { title: string; desc: string }; type TipProps = { title: string; desc: string };
const Tip = ({ title, desc }: TipProps) => { const Tip = ({ title, desc }: TipProps) => {
@@ -33,7 +34,10 @@ export default function BotConfig() {
const [createModalVisible, setCreateModalVisible] = useState(false); const [createModalVisible, setCreateModalVisible] = useState(false);
const [currWebhookParams, setCurrWebhookParams] = useState<WebhookParams | undefined>(undefined); const [currWebhookParams, setCurrWebhookParams] = useState<WebhookParams | undefined>(undefined);
const [currDeleteParams, setCurrDeleteParams] = useState<DeleteParams | undefined>(undefined); const [currDeleteParams, setCurrDeleteParams] = useState<DeleteParams | undefined>(undefined);
const bots = useAppSelector((store) => Object.values(store.users.byId).filter((u) => !!u.is_bot)); const bots = useAppSelector(
(store) => Object.values(store.users.byId).filter((u) => !!u.is_bot),
shallowEqual
);
const { t } = useTranslation("setting", { keyPrefix: "bot" }); const { t } = useTranslation("setting", { keyPrefix: "bot" });
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
@@ -11,13 +11,17 @@ import SettingBlock from "@/components/SettingBlock";
import Button from "@/components/styled/Button"; import Button from "@/components/styled/Button";
import StyledModal from "@/components/styled/Modal"; import StyledModal from "@/components/styled/Modal";
import StyledRadio from "@/components/styled/Radio"; import StyledRadio from "@/components/styled/Radio";
import { shallowEqual } from "react-redux";
// type Props = { // type Props = {
// updateConfirmModal: (context: VisibleModalType | null) => void; // updateConfirmModal: (context: VisibleModalType | null) => void;
// }; // };
const AutoDeleteFiles = () => { const AutoDeleteFiles = () => {
const currStatus = useAppSelector((store) => store.server.max_file_expiry_mode ?? "Off"); const currStatus = useAppSelector(
(store) => store.server.max_file_expiry_mode ?? "Off",
shallowEqual
);
const { t } = useTranslation("setting", { keyPrefix: "data.auto_delete_file" }); const { t } = useTranslation("setting", { keyPrefix: "data.auto_delete_file" });
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
const { refetch } = useGetSystemCommonQuery(); const { refetch } = useGetSystemCommonQuery();
+5 -3
View File
@@ -9,6 +9,7 @@ import Button from "@/components/styled/Button";
import ProfileBasicEditModal from "./ProfileBasicEditModal"; import ProfileBasicEditModal from "./ProfileBasicEditModal";
import RemoveAccountConfirmModal from "./RemoveAccountConfirmModal"; import RemoveAccountConfirmModal from "./RemoveAccountConfirmModal";
import UpdatePasswordModal from "./UpdatePasswordModal"; import UpdatePasswordModal from "./UpdatePasswordModal";
import { shallowEqual } from "react-redux";
type EditField = "name" | "email" | ""; type EditField = "name" | "email" | "";
export default function MyAccount() { export default function MyAccount() {
@@ -31,9 +32,10 @@ export default function MyAccount() {
} }
}; };
const loginUser = useAppSelector((store) => { const loginUser = useAppSelector(
return store.users.byId[store.authData.user?.uid || 0]; (store) => store.users.byId[store.authData.user?.uid || 0],
}); shallowEqual
);
useEffect(() => { useEffect(() => {
if (uploadSuccess) { if (uploadSuccess) {
+5 -1
View File
@@ -11,11 +11,15 @@ import {
} from "../../../app/services/server"; } from "../../../app/services/server";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import { ChatLayout } from "../../../types/server"; import { ChatLayout } from "../../../types/server";
import { shallowEqual } from "react-redux";
// type Props = {} // type Props = {}
const Index = () => { const Index = () => {
const currStatus = useAppSelector((store) => store.server.chat_layout_mode ?? "Left"); const currStatus = useAppSelector(
(store) => store.server.chat_layout_mode ?? "Left",
shallowEqual
);
const { t } = useTranslation("setting", { keyPrefix: "overview.chat_layout" }); const { t } = useTranslation("setting", { keyPrefix: "overview.chat_layout" });
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
const { refetch } = useGetSystemCommonQuery(); const { refetch } = useGetSystemCommonQuery();
@@ -10,11 +10,15 @@ import {
useUpdateSystemCommonMutation useUpdateSystemCommonMutation
} from "../../../app/services/server"; } from "../../../app/services/server";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import { shallowEqual } from "react-redux";
// type Props = {} // type Props = {}
const Index = () => { const Index = () => {
const currStatus = useAppSelector((store) => !!store.server.contact_verification_enable); const currStatus = useAppSelector(
(store) => !!store.server.contact_verification_enable,
shallowEqual
);
const { t } = useTranslation("setting", { keyPrefix: "overview.contact_verify" }); const { t } = useTranslation("setting", { keyPrefix: "overview.contact_verify" });
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
const { refetch } = useGetSystemCommonQuery(); const { refetch } = useGetSystemCommonQuery();
+5 -1
View File
@@ -10,11 +10,15 @@ import {
useUpdateSystemCommonMutation useUpdateSystemCommonMutation
} from "../../../app/services/server"; } from "../../../app/services/server";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import { shallowEqual } from "react-redux";
// type Props = {} // type Props = {}
const Index = () => { const Index = () => {
const currStatus = useAppSelector((store) => !!store.server.show_user_online_status); const currStatus = useAppSelector(
(store) => !!store.server.show_user_online_status,
shallowEqual
);
const { t } = useTranslation("setting", { keyPrefix: "overview.online_status" }); const { t } = useTranslation("setting", { keyPrefix: "overview.online_status" });
const { t: ct } = useTranslation(); const { t: ct } = useTranslation();
const { refetch } = useGetSystemCommonQuery(); const { refetch } = useGetSystemCommonQuery();

Some files were not shown because too many files have changed in this diff Show More