refactor: useSelector
This commit is contained in:
+1
-1
@@ -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",
|
||||||
|
|||||||
@@ -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")}
|
||||||
|
|||||||
@@ -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),
|
shallowEqual
|
||||||
loginUser: store.authData.user,
|
);
|
||||||
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();
|
||||||
|
|
||||||
|
|||||||
@@ -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 +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;
|
||||||
|
|||||||
@@ -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: "",
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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 ({
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
// 未登录
|
// 未登录
|
||||||
|
|||||||
@@ -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!");
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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,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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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}`}</>;
|
||||||
|
|||||||
@@ -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] ?? {};
|
||||||
|
|||||||
@@ -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 });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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}']`);
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,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>}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 />;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
context == "channel"
|
||||||
videoInputDevices,
|
? store.footprint.channelAsides[id] == "voice_fullscreen"
|
||||||
audioInputDeviceId,
|
: store.footprint.dmAsides[id] == "voice_fullscreen",
|
||||||
audioOutputDeviceId,
|
shallowEqual
|
||||||
videoInputDeviceId,
|
);
|
||||||
fullscreen
|
const audioInputDevices = useAppSelector(
|
||||||
} = useAppSelector((store) => {
|
(store) => store.voice.devices.filter((d) => d.kind == "audioinput") ?? [],
|
||||||
return {
|
shallowEqual
|
||||||
fullscreen:
|
);
|
||||||
context == "channel"
|
const audioOutputDevices = useAppSelector(
|
||||||
? store.footprint.channelAsides[id] == "voice_fullscreen"
|
(store) => store.voice.devices.filter((d) => d.kind == "audiooutput") ?? [],
|
||||||
: store.footprint.dmAsides[id] == "voice_fullscreen",
|
shallowEqual
|
||||||
loginUid: store.authData.user?.uid ?? 0,
|
);
|
||||||
voicingInfo: store.voice.voicing,
|
const videoInputDevices = useAppSelector(
|
||||||
audioInputDevices: store.voice.devices.filter((d) => d.kind == "audioinput") ?? [],
|
(store) => store.voice.devices.filter((d) => d.kind == "videoinput") ?? [],
|
||||||
audioOutputDevices: store.voice.devices.filter((d) => d.kind == "audiooutput") ?? [],
|
shallowEqual
|
||||||
videoInputDevices: store.voice.devices.filter((d) => d.kind == "videoinput") ?? [],
|
);
|
||||||
audioInputDeviceId: store.voice.audioInputDeviceId,
|
const audioInputDeviceId = useAppSelector(
|
||||||
audioOutputDeviceId: store.voice.audioOutputDeviceId,
|
(store) => store.voice.audioInputDeviceId,
|
||||||
videoInputDeviceId: store.voice.videoInputDeviceId
|
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 () => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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(() => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,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
@@ -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,
|
||||||
|
|||||||
@@ -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 [
|
||||||
|
|||||||
@@ -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 +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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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不存在了 回首页
|
||||||
|
|||||||
@@ -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不存在了 回首页
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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 });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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],
|
shallowEqual
|
||||||
mids: context == "dm" ? store.userMessage.byId[id] : store.channelMessage[id],
|
);
|
||||||
selects: store.ui.selectMessages[`${context}_${id}`],
|
const mids = useAppSelector(
|
||||||
loginUid: store.authData.user?.uid ?? 0,
|
(store) => (context == "dm" ? store.userMessage.byId[id] : store.channelMessage[id]),
|
||||||
data: context == "channel" ? store.channels.byId[id] : undefined,
|
shallowEqual
|
||||||
messageData: store.message || {}
|
);
|
||||||
};
|
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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 `;
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 }] =
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 }] =
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user