style: format ts file with prettier
This commit is contained in:
Vendored
+2
-2
@@ -1,8 +1,8 @@
|
||||
import * as localforage from "localforage";
|
||||
import { extendPrototype } from 'localforage-setitems';
|
||||
import { extendPrototype } from "localforage-setitems";
|
||||
|
||||
import { CACHE_VERSION, KEY_UID } from "../config";
|
||||
import useRehydrate from "./useRehydrate";
|
||||
import { KEY_UID, CACHE_VERSION } from "../config";
|
||||
|
||||
extendPrototype(localforage);
|
||||
const tables = [
|
||||
|
||||
Vendored
+7
-6
@@ -1,15 +1,16 @@
|
||||
import { useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { fillReactionMessage } from "../slices/message.reaction";
|
||||
import { fillServer } from "../slices/server";
|
||||
|
||||
import { fillChannels } from "../slices/channels";
|
||||
import { fillFootprint } from "../slices/footprint";
|
||||
import { fillMessage } from "../slices/message";
|
||||
import { fillChannelMsg } from "../slices/message.channel";
|
||||
import { fillUserMsg } from "../slices/message.user";
|
||||
import { fillChannels } from "../slices/channels";
|
||||
import { fillUsers } from "../slices/users";
|
||||
import { fillFootprint } from "../slices/footprint";
|
||||
import { fillFileMessage } from "../slices/message.file";
|
||||
import { fillReactionMessage } from "../slices/message.reaction";
|
||||
import { fillUserMsg } from "../slices/message.user";
|
||||
import { fillServer } from "../slices/server";
|
||||
import { fillUI } from "../slices/ui";
|
||||
import { fillUsers } from "../slices/users";
|
||||
|
||||
const useRehydrate = () => {
|
||||
const [iterated, setIterated] = useState(false);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
interface Params {
|
||||
payload: any;
|
||||
operation: string;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import localforage from "localforage";
|
||||
|
||||
import { Channel } from "@/types/channel";
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
interface Params {
|
||||
data: any;
|
||||
payload: any;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
interface Params {
|
||||
data: any;
|
||||
payload: any;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
interface Params {
|
||||
data: any;
|
||||
operation: string;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import localforage from "localforage";
|
||||
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
interface Params {
|
||||
payload: any;
|
||||
data: any;
|
||||
@@ -7,7 +9,7 @@ interface Params {
|
||||
}
|
||||
const ignores = ["voice_fullscreen", "voice"];
|
||||
export default async function handler({ operation, data = {}, payload }: Params) {
|
||||
const table = window.CACHE["footprint"] as typeof localforage;;
|
||||
const table = window.CACHE["footprint"] as typeof localforage;
|
||||
if (operation.startsWith("reset")) {
|
||||
clearTable("footprint");
|
||||
return;
|
||||
@@ -34,7 +36,6 @@ export default async function handler({ operation, data = {}, payload }: Params)
|
||||
table?.setItem("afterMid", afterMid);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
break;
|
||||
case "updateMute":
|
||||
@@ -48,7 +49,6 @@ export default async function handler({ operation, data = {}, payload }: Params)
|
||||
const { type } = payload;
|
||||
if (type == "channel") {
|
||||
await table?.setItem("historyChannels", data.historyChannels);
|
||||
|
||||
} else {
|
||||
await table?.setItem("historyUsers", data.historyUsers);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import localforage from "localforage";
|
||||
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
interface Params {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import localforage from "localforage";
|
||||
|
||||
import { ContactStatus, User } from "@/types/user";
|
||||
import clearTable from "./clear.handler";
|
||||
|
||||
@@ -20,7 +21,9 @@ export default async function handler({ operation, data, payload }) {
|
||||
break;
|
||||
case "updateContactStatus":
|
||||
{
|
||||
const tmp = payload as { uid: number, status: ContactStatus } | { uid: number, status: ContactStatus }[];
|
||||
const tmp = payload as
|
||||
| { uid: number; status: ContactStatus }
|
||||
| { uid: number; status: ContactStatus }[];
|
||||
const arr = Array.isArray(tmp) ? tmp : [tmp];
|
||||
const opts = arr.map(({ uid, status }) => {
|
||||
return { key: uid + "", value: { ...data.byId[uid], status } };
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { createListenerMiddleware } from "@reduxjs/toolkit";
|
||||
import rtkqHandler from "./handler.rtkq";
|
||||
import channelsHandler from "./handler.channels";
|
||||
import usersHandler from "./handler.users";
|
||||
import channelMsgHandler from "./handler.channel.msg";
|
||||
import dmMsgHandler from "./handler.dm.msg";
|
||||
import serverHandler from "./handler.server";
|
||||
import messageHandler from "./handler.message";
|
||||
import fileMessageHandler from "./handler.file.msg";
|
||||
import archiveMessageHandler from "./handler.archive.msg";
|
||||
import reactionHandler from "./handler.reaction";
|
||||
import UIHandler from "./handler.ui";
|
||||
import footprintHandler from "./handler.footprint";
|
||||
|
||||
import { RootState } from "../store";
|
||||
import archiveMessageHandler from "./handler.archive.msg";
|
||||
import channelMsgHandler from "./handler.channel.msg";
|
||||
import channelsHandler from "./handler.channels";
|
||||
import dmMsgHandler from "./handler.dm.msg";
|
||||
import fileMessageHandler from "./handler.file.msg";
|
||||
import footprintHandler from "./handler.footprint";
|
||||
import messageHandler from "./handler.message";
|
||||
import reactionHandler from "./handler.reaction";
|
||||
import rtkqHandler from "./handler.rtkq";
|
||||
import serverHandler from "./handler.server";
|
||||
import UIHandler from "./handler.ui";
|
||||
import usersHandler from "./handler.users";
|
||||
|
||||
const operations = [
|
||||
"__rtkq",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { nanoid } from "@reduxjs/toolkit";
|
||||
import baseQuery from "./base.query";
|
||||
import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
|
||||
import BASE_URL, { KEY_DEVICE_ID, KEY_DEVICE_TOKEN, KEY_LOCAL_MAGIC_TOKEN } from "../config";
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
import {
|
||||
AuthData,
|
||||
CredentialResponse,
|
||||
@@ -11,6 +9,9 @@ import {
|
||||
RenewTokenResponse
|
||||
} from "@/types/auth";
|
||||
import { UserRegDTO, UserRegResponse } from "@/types/user";
|
||||
import BASE_URL, { KEY_DEVICE_ID, KEY_DEVICE_TOKEN, KEY_LOCAL_MAGIC_TOKEN } from "../config";
|
||||
import { resetAuthData, setAuthData, updateInitialized, updateToken } from "../slices/auth.data";
|
||||
import baseQuery from "./base.query";
|
||||
|
||||
const getDeviceId = () => {
|
||||
let d = localStorage.getItem(KEY_DEVICE_ID);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { fetchBaseQuery } from "@reduxjs/toolkit/query";
|
||||
import toast from "react-hot-toast";
|
||||
import { fetchBaseQuery } from "@reduxjs/toolkit/query";
|
||||
import dayjs from "dayjs";
|
||||
import { updateToken, resetAuthData } from "../slices/auth.data";
|
||||
import BASE_URL, { tokenHeader } from "../config";
|
||||
import { RootState } from "../store";
|
||||
|
||||
import { getLocalAuthData } from "@/utils";
|
||||
import BASE_URL, { tokenHeader } from "../config";
|
||||
import { resetAuthData, updateToken } from "../slices/auth.data";
|
||||
import { RootState } from "../store";
|
||||
|
||||
const whiteList = [
|
||||
"guestLogin",
|
||||
@@ -102,12 +103,13 @@ const baseQueryWithTokenCheck = async (args: any, api: any, extraOptions: any) =
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 403: {
|
||||
const whiteList403 = ["sendMsg"];
|
||||
if (!whiteList403.includes(api.endpoint)) {
|
||||
toast.error("Request Not Allowed");
|
||||
case 403:
|
||||
{
|
||||
const whiteList403 = ["sendMsg"];
|
||||
if (!whiteList403.includes(api.endpoint)) {
|
||||
toast.error("Request Not Allowed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 404:
|
||||
|
||||
+22
-18
@@ -1,16 +1,17 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
// import toast from "react-hot-toast";
|
||||
import baseQuery from "./base.query";
|
||||
|
||||
import { Channel, ChannelDTO, CreateChannelDTO } from "@/types/channel";
|
||||
import { ContentTypeKey } from "@/types/message";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { updateChannel, removeChannel } from "../slices/channels";
|
||||
import { updateRememberedNavs } from "../slices/ui";
|
||||
import { removeChannel, updateChannel } from "../slices/channels";
|
||||
import { removeMessage } from "../slices/message";
|
||||
import { removeChannelSession } from "../slices/message.channel";
|
||||
import { removeReactionMessage } from "../slices/message.reaction";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
import { Channel, ChannelDTO, CreateChannelDTO } from "@/types/channel";
|
||||
import { updateRememberedNavs } from "../slices/ui";
|
||||
import { RootState } from "../store";
|
||||
import { ContentTypeKey } from "@/types/message";
|
||||
// import toast from "react-hot-toast";
|
||||
import baseQuery from "./base.query";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
|
||||
export const channelApi = createApi({
|
||||
reducerPath: "channelApi",
|
||||
@@ -34,14 +35,19 @@ export const channelApi = createApi({
|
||||
}
|
||||
}
|
||||
}),
|
||||
createChannel: builder.mutation<{ gid: number, created_at: number } | number, CreateChannelDTO>({
|
||||
query: (data) => ({
|
||||
url: "group",
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
}),
|
||||
changeChannelType: builder.mutation<number, { is_public: boolean, id: number, members?: number[] }>({
|
||||
createChannel: builder.mutation<{ gid: number; created_at: number } | number, CreateChannelDTO>(
|
||||
{
|
||||
query: (data) => ({
|
||||
url: "group",
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
}
|
||||
),
|
||||
changeChannelType: builder.mutation<
|
||||
number,
|
||||
{ is_public: boolean; id: number; members?: number[] }
|
||||
>({
|
||||
query: ({ id, is_public, members }) => ({
|
||||
url: `/group/${id}/change_type`,
|
||||
method: "POST",
|
||||
@@ -101,9 +107,7 @@ export const channelApi = createApi({
|
||||
method: "DELETE"
|
||||
}),
|
||||
async onQueryStarted(id, { dispatch, getState, queryFulfilled }) {
|
||||
const {
|
||||
channelMessage,
|
||||
} = getState() as RootState;
|
||||
const { channelMessage } = getState() as RootState;
|
||||
try {
|
||||
await queryFulfilled;
|
||||
// 删掉该channel下的所有消息&reaction
|
||||
|
||||
@@ -2,9 +2,9 @@ import toast from "react-hot-toast";
|
||||
import { batch } from "react-redux";
|
||||
|
||||
import { ContentTypes } from "../config";
|
||||
import { addMessage, removeMessage, updateMessage } from "../slices/message";
|
||||
import { addChannelMsg, removeChannelMsg } from "../slices/message.channel";
|
||||
import { addUserMsg, removeUserMsg } from "../slices/message.user";
|
||||
import { addMessage, removeMessage, updateMessage } from "../slices/message";
|
||||
|
||||
export const onMessageSendStarted = async (
|
||||
{
|
||||
@@ -23,7 +23,9 @@ export const onMessageSendStarted = async (
|
||||
if (type == "archive") return;
|
||||
// id: who send to ,from_uid: who sent
|
||||
// console.log("handlers data", content, type, properties, ignoreLocal, id);
|
||||
const isMedia = properties.content_type ? ["image", "video", "audio"].includes(properties.content_type.split('/')[0]) : false;
|
||||
const isMedia = properties.content_type
|
||||
? ["image", "video", "audio"].includes(properties.content_type.split("/")[0])
|
||||
: false;
|
||||
// const isImage = properties.content_type?.startsWith("image");
|
||||
const ts = properties.local_id || +new Date();
|
||||
const tmpMsg = {
|
||||
@@ -63,7 +65,7 @@ export const onMessageSendStarted = async (
|
||||
// toast.error(`Failed to send, blocked maybe.`,);
|
||||
dispatch(updateMessage({ mid: ts, failed: true }));
|
||||
} else {
|
||||
toast.error(`Send Message Failed ${JSON.stringify(error)}`,);
|
||||
toast.error(`Send Message Failed ${JSON.stringify(error)}`);
|
||||
dispatch(removeContextMessage({ id, mid: ts }));
|
||||
dispatch(removeMessage(ts));
|
||||
}
|
||||
|
||||
+38
-25
@@ -1,16 +1,17 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { ContentTypes } from "../config";
|
||||
import { updateReadChannels, updateReadUsers, upsertOG } from "../slices/footprint";
|
||||
import { fillFavorites, populateFavorite, addFavorite, deleteFavorite } from "../slices/favorites";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
import { normalizeArchiveData } from "@/utils";
|
||||
import baseQuery from "./base.query";
|
||||
import { Archive, FavoriteArchive, OG } from "@/types/resource";
|
||||
import { ChatMessage, ContentTypeKey, UploadFileResponse } from "@/types/message";
|
||||
|
||||
import { ChatContext } from "@/types/common";
|
||||
import { ChatMessage, ContentTypeKey, UploadFileResponse } from "@/types/message";
|
||||
import { Archive, FavoriteArchive, OG } from "@/types/resource";
|
||||
import handleChatMessage from "@/hooks/useStreaming/chat.handler";
|
||||
import { RootState } from "../store";
|
||||
import { normalizeArchiveData } from "@/utils";
|
||||
import { ContentTypes } from "../config";
|
||||
import { addFavorite, deleteFavorite, fillFavorites, populateFavorite } from "../slices/favorites";
|
||||
import { updateReadChannels, updateReadUsers, upsertOG } from "../slices/footprint";
|
||||
import { upsertArchiveMessage } from "../slices/message.archive";
|
||||
import { RootState } from "../store";
|
||||
import baseQuery from "./base.query";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
|
||||
export const messageApi = createApi({
|
||||
reducerPath: "messageApi",
|
||||
@@ -73,15 +74,18 @@ export const messageApi = createApi({
|
||||
dispatch(upsertOG({ key: url, value: data }));
|
||||
} catch (err) {
|
||||
console.log("get og error", err);
|
||||
dispatch(upsertOG({
|
||||
key: url, value: {
|
||||
images: [],
|
||||
audios: [],
|
||||
videos: [],
|
||||
title: "",
|
||||
url,
|
||||
}
|
||||
}));
|
||||
dispatch(
|
||||
upsertOG({
|
||||
key: url,
|
||||
value: {
|
||||
images: [],
|
||||
audios: [],
|
||||
videos: [],
|
||||
title: "",
|
||||
url
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -176,12 +180,15 @@ export const messageApi = createApi({
|
||||
}
|
||||
}
|
||||
}),
|
||||
loadMoreMessages: builder.query<ChatMessage[], { context?: ChatContext, id: number; mid?: number; limit?: number }>({
|
||||
loadMoreMessages: builder.query<
|
||||
ChatMessage[],
|
||||
{ context?: ChatContext; id: number; mid?: number; limit?: number }
|
||||
>({
|
||||
query: ({ context = "channel", id, mid = "", limit = 100 }) => {
|
||||
const url = context == "channel" ?
|
||||
`/group/${id}/history?limit=${limit}${mid ? `&before=${mid}` : ""}`
|
||||
:
|
||||
`/user/${id}/history?limit=${limit}${mid ? `&before=${mid}` : ""}`;
|
||||
const url =
|
||||
context == "channel"
|
||||
? `/group/${id}/history?limit=${limit}${mid ? `&before=${mid}` : ""}`
|
||||
: `/user/${id}/history?limit=${limit}${mid ? `&before=${mid}` : ""}`;
|
||||
return {
|
||||
url
|
||||
};
|
||||
@@ -198,7 +205,13 @@ export const messageApi = createApi({
|
||||
}),
|
||||
replyMessage: builder.mutation<
|
||||
number,
|
||||
{ from_uid: number, reply_mid: number; content: string; type: ContentTypeKey, properties?: {} }
|
||||
{
|
||||
from_uid: number;
|
||||
reply_mid: number;
|
||||
content: string;
|
||||
type: ContentTypeKey;
|
||||
properties?: {};
|
||||
}
|
||||
>({
|
||||
query: ({ reply_mid, content, type = "text", properties }) => ({
|
||||
headers: {
|
||||
@@ -245,7 +258,7 @@ export const messageApi = createApi({
|
||||
method: "HEAD",
|
||||
responseHandler: "text"
|
||||
})
|
||||
}),
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
+61
-46
@@ -1,30 +1,31 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import BASE_URL, { ContentTypes, IS_OFFICIAL_DEMO, PAYMENT_URL_PREFIX } from "../config";
|
||||
import { updateInfo } from "../slices/server";
|
||||
import baseQuery from "./base.query";
|
||||
import { RootState } from "../store";
|
||||
import { User } from "@/types/user";
|
||||
import {
|
||||
FirebaseConfig,
|
||||
GoogleAuthConfig,
|
||||
LoginConfig,
|
||||
Server,
|
||||
TestEmailDTO,
|
||||
CreateAdminDTO,
|
||||
SMTPConfig,
|
||||
AgoraConfig,
|
||||
GithubAuthConfig,
|
||||
LicenseResponse,
|
||||
RenewLicense,
|
||||
RenewLicenseResponse,
|
||||
AgoraTokenResponse,
|
||||
AgoraVoicingListResponse,
|
||||
SystemCommon,
|
||||
AgoraChannelUsersResponse
|
||||
} from "@/types/server";
|
||||
|
||||
import { Channel } from "@/types/channel";
|
||||
import { ContentTypeKey } from "@/types/message";
|
||||
import {
|
||||
AgoraChannelUsersResponse,
|
||||
AgoraConfig,
|
||||
AgoraTokenResponse,
|
||||
AgoraVoicingListResponse,
|
||||
CreateAdminDTO,
|
||||
FirebaseConfig,
|
||||
GithubAuthConfig,
|
||||
GoogleAuthConfig,
|
||||
LicenseResponse,
|
||||
LoginConfig,
|
||||
RenewLicense,
|
||||
RenewLicenseResponse,
|
||||
Server,
|
||||
SMTPConfig,
|
||||
SystemCommon,
|
||||
TestEmailDTO
|
||||
} from "@/types/server";
|
||||
import { User } from "@/types/user";
|
||||
import BASE_URL, { ContentTypes, IS_OFFICIAL_DEMO, PAYMENT_URL_PREFIX } from "../config";
|
||||
import { updateInfo } from "../slices/server";
|
||||
import { updateCallInfo, upsertVoiceList } from "../slices/voice";
|
||||
import { RootState } from "../store";
|
||||
import baseQuery from "./base.query";
|
||||
|
||||
const defaultExpireDuration = 2 * 24 * 60 * 60;
|
||||
|
||||
@@ -69,9 +70,7 @@ export const serverApi = createApi({
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const resp = await queryFulfilled;
|
||||
dispatch(
|
||||
updateInfo({ version: resp.data })
|
||||
);
|
||||
dispatch(updateInfo({ version: resp.data }));
|
||||
} catch {
|
||||
console.error("get server version error");
|
||||
}
|
||||
@@ -117,18 +116,26 @@ export const serverApi = createApi({
|
||||
getAgoraConfig: builder.query<AgoraConfig, void>({
|
||||
query: () => ({ url: `/admin/agora/config` })
|
||||
}),
|
||||
getAgoraChannels: builder.query<AgoraVoicingListResponse, { page_no: number, page_size: number }>({
|
||||
query: (param = { page_no: 0, page_size: 100 }) => ({ url: `/admin/agora/channel/${param.page_no}/${param.page_size}` }),
|
||||
getAgoraChannels: builder.query<
|
||||
AgoraVoicingListResponse,
|
||||
{ page_no: number; page_size: number }
|
||||
>({
|
||||
query: (param = { page_no: 0, page_size: 100 }) => ({
|
||||
url: `/admin/agora/channel/${param.page_no}/${param.page_size}`
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
|
||||
try {
|
||||
const { voice: { callingFrom }, authData } = getState() as RootState;
|
||||
const {
|
||||
voice: { callingFrom },
|
||||
authData
|
||||
} = getState() as RootState;
|
||||
const { data: resp } = await queryFulfilled;
|
||||
const { success } = resp;
|
||||
if (success) {
|
||||
const arr = resp.data.channels.map(data => {
|
||||
const arr = resp.data.channels.map((data) => {
|
||||
const [type, id] = data.channel_name.split(":").slice(-2);
|
||||
const count = data.user_count;
|
||||
const context = type === "group" ? "channel" as const : "dm" as const;
|
||||
const context = type === "group" ? ("channel" as const) : ("dm" as const);
|
||||
return {
|
||||
id: +id,
|
||||
context,
|
||||
@@ -137,7 +144,9 @@ export const serverApi = createApi({
|
||||
};
|
||||
});
|
||||
dispatch(upsertVoiceList(arr));
|
||||
const hasMyself = arr.some(data => data.context === "dm" && data.id == authData?.user?.uid);
|
||||
const hasMyself = arr.some(
|
||||
(data) => data.context === "dm" && data.id == authData?.user?.uid
|
||||
);
|
||||
const sendByMe = callingFrom && callingFrom === authData?.user?.uid;
|
||||
// reset dm call setting
|
||||
if (callingFrom && !sendByMe && !hasMyself) {
|
||||
@@ -187,9 +196,7 @@ export const serverApi = createApi({
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const resp = await queryFulfilled;
|
||||
dispatch(
|
||||
updateInfo(resp.data)
|
||||
);
|
||||
dispatch(updateInfo(resp.data));
|
||||
} catch {
|
||||
console.error("get server common error");
|
||||
}
|
||||
@@ -204,9 +211,7 @@ export const serverApi = createApi({
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled;
|
||||
dispatch(
|
||||
updateInfo(data)
|
||||
);
|
||||
dispatch(updateInfo(data));
|
||||
} catch {
|
||||
console.error("update server common error");
|
||||
}
|
||||
@@ -309,7 +314,7 @@ export const serverApi = createApi({
|
||||
url: `/admin/system/update_frontend_url`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": 'text/plain',
|
||||
"content-type": "text/plain"
|
||||
},
|
||||
body: url
|
||||
})
|
||||
@@ -324,16 +329,16 @@ export const serverApi = createApi({
|
||||
const rootStore = getState() as RootState;
|
||||
const { upgraded: prevValue } = rootStore.server;
|
||||
try {
|
||||
const { data: { user_limit } } = await queryFulfilled;
|
||||
const {
|
||||
data: { user_limit }
|
||||
} = await queryFulfilled;
|
||||
const currValue = user_limit > 20;
|
||||
if (prevValue !== currValue) {
|
||||
dispatch(updateInfo({ upgraded: currValue }));
|
||||
}
|
||||
} catch {
|
||||
console.error("update license upgraded status failed ");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -363,15 +368,25 @@ export const serverApi = createApi({
|
||||
body: { license }
|
||||
})
|
||||
}),
|
||||
getBotRelatedChannels: builder.query<Channel[], { api_key: string, public_only?: boolean }>({
|
||||
getBotRelatedChannels: builder.query<Channel[], { api_key: string; public_only?: boolean }>({
|
||||
query: ({ api_key, public_only = false }) => ({
|
||||
url: public_only ? `/bot?public_only=${public_only}` : `/bot`,
|
||||
headers: {
|
||||
"x-api-key": api_key
|
||||
},
|
||||
}
|
||||
})
|
||||
}),
|
||||
sendMessageByBot: builder.mutation<number, { uid?: number, cid?: number, api_key: string, content: string, type?: ContentTypeKey, properties?: object }>({
|
||||
sendMessageByBot: builder.mutation<
|
||||
number,
|
||||
{
|
||||
uid?: number;
|
||||
cid?: number;
|
||||
api_key: string;
|
||||
content: string;
|
||||
type?: ContentTypeKey;
|
||||
properties?: object;
|
||||
}
|
||||
>({
|
||||
query: ({ uid, cid, api_key, type = "text", properties, content }) => ({
|
||||
headers: {
|
||||
"x-api-key": api_key,
|
||||
@@ -384,7 +399,7 @@ export const serverApi = createApi({
|
||||
method: "POST",
|
||||
body: content
|
||||
})
|
||||
}),
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
+44
-28
@@ -1,13 +1,25 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
// import toast from "react-hot-toast";
|
||||
import baseQuery from "./base.query";
|
||||
|
||||
import { ContentTypeKey, MuteDTO } from "@/types/message";
|
||||
import {
|
||||
AutoDeleteMsgDTO,
|
||||
BotAPIKey,
|
||||
ContactAction,
|
||||
ContactResponse,
|
||||
ContactStatus,
|
||||
User,
|
||||
UserCreateDTO,
|
||||
UserDTO,
|
||||
UserForAdmin,
|
||||
UserForAdminDTO
|
||||
} from "@/types/user";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { updateAutoDeleteSetting, updateMute } from "../slices/footprint";
|
||||
import { fillUsers, updateContactStatus as updateStatus } from "../slices/users";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
import { AutoDeleteMsgDTO, BotAPIKey, ContactAction, ContactResponse, ContactStatus, User, UserCreateDTO, UserDTO, UserForAdmin, UserForAdminDTO } from "@/types/user";
|
||||
import { ContentTypeKey, MuteDTO } from "@/types/message";
|
||||
import { RootState } from "../store";
|
||||
// import toast from "react-hot-toast";
|
||||
import baseQuery from "./base.query";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
|
||||
export const userApi = createApi({
|
||||
reducerPath: "userApi",
|
||||
@@ -29,14 +41,20 @@ export const userApi = createApi({
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
|
||||
try {
|
||||
const { data: users } = await queryFulfilled;
|
||||
const { authData: { user: loginUser } } = getState() as RootState;
|
||||
dispatch(fillUsers(users.map((u) => {
|
||||
const status = loginUser?.uid == u.uid ? "added" : "";
|
||||
return {
|
||||
...u,
|
||||
status
|
||||
};
|
||||
})));
|
||||
const {
|
||||
authData: { user: loginUser }
|
||||
} = getState() as RootState;
|
||||
dispatch(
|
||||
fillUsers(
|
||||
users.map((u) => {
|
||||
const status = loginUser?.uid == u.uid ? "added" : "";
|
||||
return {
|
||||
...u,
|
||||
status
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
console.log("get user list error");
|
||||
}
|
||||
@@ -71,7 +89,7 @@ export const userApi = createApi({
|
||||
method: "POST"
|
||||
})
|
||||
}),
|
||||
searchUser: builder.mutation<User, { search_type: "id" | "email", keyword: string }>({
|
||||
searchUser: builder.mutation<User, { search_type: "id" | "email"; keyword: string }>({
|
||||
query: (input) => ({
|
||||
url: `/user/search`,
|
||||
body: input,
|
||||
@@ -123,7 +141,7 @@ export const userApi = createApi({
|
||||
}
|
||||
}),
|
||||
|
||||
updateContactStatus: builder.mutation<void, { action: ContactAction, target_uid: number }>({
|
||||
updateContactStatus: builder.mutation<void, { action: ContactAction; target_uid: number }>({
|
||||
query: (payload) => ({
|
||||
url: `/user/update_contact_status`,
|
||||
method: "POST",
|
||||
@@ -131,10 +149,10 @@ export const userApi = createApi({
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
const map = {
|
||||
"add": "added",
|
||||
"block": "blocked",
|
||||
"remove": "",
|
||||
"unblock": "",
|
||||
add: "added",
|
||||
block: "blocked",
|
||||
remove: "",
|
||||
unblock: ""
|
||||
};
|
||||
try {
|
||||
await queryFulfilled;
|
||||
@@ -170,7 +188,7 @@ export const userApi = createApi({
|
||||
body: data
|
||||
})
|
||||
}),
|
||||
updateAvatarByAdmin: builder.mutation<void, { uid: number, file: File }>({
|
||||
updateAvatarByAdmin: builder.mutation<void, { uid: number; file: File }>({
|
||||
query: ({ uid, file }) => ({
|
||||
headers: {
|
||||
"content-type": "image/png"
|
||||
@@ -184,18 +202,17 @@ export const userApi = createApi({
|
||||
query: (uid) => ({ url: `/admin/user/${uid}` })
|
||||
}),
|
||||
// bot operations
|
||||
createBotAPIKey: builder.mutation<void, { uid: number, name: string }>({
|
||||
createBotAPIKey: builder.mutation<void, { uid: number; name: string }>({
|
||||
query: ({ uid, name }) => ({
|
||||
url: `/admin/user/bot-api-key/${uid}`,
|
||||
method: "POST",
|
||||
body: { name },
|
||||
}),
|
||||
|
||||
body: { name }
|
||||
})
|
||||
}),
|
||||
getBotAPIKeys: builder.query<BotAPIKey[], number>({
|
||||
query: (uid) => ({ url: `/admin/user/bot-api-key/${uid}` })
|
||||
}),
|
||||
deleteBotAPIKey: builder.query<void, { uid: number, kid: number }>({
|
||||
deleteBotAPIKey: builder.query<void, { uid: number; kid: number }>({
|
||||
query: ({ uid, kid }) => ({ url: `/admin/user/bot-api-key/${uid}/${kid}`, method: "DELETE" })
|
||||
}),
|
||||
// bot operations end
|
||||
@@ -225,8 +242,7 @@ export const userApi = createApi({
|
||||
// @ts-ignore
|
||||
await onMessageSendStarted.call(this, param1, param2, "user");
|
||||
}
|
||||
}),
|
||||
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
import { AuthData, RenewTokenResponse } from "@/types/auth";
|
||||
// import { isNull, omitBy } from "lodash";
|
||||
import {
|
||||
KEY_EXPIRE,
|
||||
KEY_PWA_INSTALLED,
|
||||
KEY_LOGIN_USER,
|
||||
KEY_PWA_INSTALLED,
|
||||
KEY_REFRESH_TOKEN,
|
||||
KEY_TOKEN,
|
||||
KEY_UID
|
||||
} from "../config";
|
||||
import { AuthData, RenewTokenResponse } from "@/types/auth";
|
||||
import { StoredUser } from "./users";
|
||||
|
||||
// import { updateUsersByLogs } from './users';
|
||||
|
||||
interface State {
|
||||
@@ -71,7 +73,7 @@ const authDataSlice = createSlice({
|
||||
if (!state.user) return;
|
||||
|
||||
const obj = { ...state.user, ...payload };
|
||||
Object.keys(obj).forEach(key => {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
// @ts-ignore
|
||||
if (obj[key] === undefined) {
|
||||
// @ts-ignore
|
||||
@@ -108,7 +110,7 @@ const authDataSlice = createSlice({
|
||||
localStorage.setItem(KEY_TOKEN, token);
|
||||
localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token);
|
||||
}
|
||||
},
|
||||
}
|
||||
// extraReducers: (builder) => {
|
||||
// builder.addCase(updateUsersByLogs, (state, action) => {
|
||||
// const changeLogs = action.payload;
|
||||
@@ -131,5 +133,12 @@ const authDataSlice = createSlice({
|
||||
// }
|
||||
});
|
||||
|
||||
export const { updateInitialized, updateLoginUser, setAuthData, resetAuthData, updateToken, updateRoleChanged } = authDataSlice.actions;
|
||||
export const {
|
||||
updateInitialized,
|
||||
updateLoginUser,
|
||||
setAuthData,
|
||||
resetAuthData,
|
||||
updateToken,
|
||||
updateRoleChanged
|
||||
} = authDataSlice.actions;
|
||||
export default authDataSlice.reducer;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { isNull, omitBy } from "lodash";
|
||||
import BASE_URL from "../config";
|
||||
|
||||
import { Channel, UpdateChannelDTO, UpdatePinnedMessageDTO } from "@/types/channel";
|
||||
import BASE_URL from "../config";
|
||||
|
||||
// import { updateVoicingInfo } from "./voice";
|
||||
|
||||
interface StoredChannel extends Channel {
|
||||
@@ -35,7 +37,7 @@ const channelsSlice = createSlice({
|
||||
icon:
|
||||
c.avatar_updated_at == 0
|
||||
? ""
|
||||
: `${BASE_URL}/resource/group_avatar?gid=${c.gid}&t=${c.avatar_updated_at}`,
|
||||
: `${BASE_URL}/resource/group_avatar?gid=${c.gid}&t=${c.avatar_updated_at}`
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -50,7 +52,7 @@ const channelsSlice = createSlice({
|
||||
icon:
|
||||
avatar_updated_at == 0
|
||||
? ""
|
||||
: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`,
|
||||
: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`
|
||||
};
|
||||
},
|
||||
updateChannel(state, action: PayloadAction<UpdateChannelDTO>) {
|
||||
@@ -109,8 +111,7 @@ const channelsSlice = createSlice({
|
||||
delete state.byId[gid];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
@@ -119,7 +120,7 @@ export const {
|
||||
fillChannels,
|
||||
addChannel,
|
||||
updateChannel,
|
||||
removeChannel,
|
||||
removeChannel
|
||||
} = channelsSlice.actions;
|
||||
|
||||
export default channelsSlice.reducer;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
// import BASE_URL from "../config";
|
||||
|
||||
export interface Favorite {
|
||||
|
||||
+27
-16
@@ -1,14 +1,23 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
import { ChatContext } from "@/types/common";
|
||||
import { MuteDTO } from "@/types/message";
|
||||
import { OG } from "@/types/resource";
|
||||
import { AutoDeleteMessageSettingDTO, AutoDeleteMsgForGroup, AutoDeleteMsgForUser, AutoDeleteSettingForChannels, AutoDeleteSettingForUsers, PinChat, PinChatTarget } from "@/types/sse";
|
||||
import {
|
||||
AutoDeleteMessageSettingDTO,
|
||||
AutoDeleteMsgForGroup,
|
||||
AutoDeleteMsgForUser,
|
||||
AutoDeleteSettingForChannels,
|
||||
AutoDeleteSettingForUsers,
|
||||
PinChat,
|
||||
PinChatTarget
|
||||
} from "@/types/sse";
|
||||
import { resetAuthData } from "./auth.data";
|
||||
import { ChatContext } from "@/types/common";
|
||||
|
||||
type ChannelAside = "members" | "voice" | "voice_fullscreen" | null;
|
||||
type DMAside = "voice" | "voice_fullscreen" | null;
|
||||
export interface State {
|
||||
og: { [url: string]: OG }
|
||||
og: { [url: string]: OG };
|
||||
usersVersion: number;
|
||||
afterMid: number;
|
||||
historyUsers: { [uid: number]: string | "reached" };
|
||||
@@ -24,7 +33,6 @@ export interface State {
|
||||
pinChats: PinChat[];
|
||||
}
|
||||
|
||||
|
||||
const initialState: State = {
|
||||
og: {},
|
||||
usersVersion: 0,
|
||||
@@ -99,9 +107,9 @@ const footprintSlice = createSlice({
|
||||
switch (key) {
|
||||
case "burn_after_reading_users": {
|
||||
const updates = (payload as AutoDeleteSettingForUsers).burn_after_reading_users;
|
||||
updates.map(item => {
|
||||
updates.map((item) => {
|
||||
const { uid } = item;
|
||||
const idx = state.autoDeleteMsgUsers.findIndex(tmp => tmp.uid == uid);
|
||||
const idx = state.autoDeleteMsgUsers.findIndex((tmp) => tmp.uid == uid);
|
||||
if (idx !== -1) {
|
||||
// update
|
||||
state.autoDeleteMsgUsers[idx] = item;
|
||||
@@ -114,9 +122,9 @@ const footprintSlice = createSlice({
|
||||
}
|
||||
case "burn_after_reading_groups": {
|
||||
const updates = (payload as AutoDeleteSettingForChannels).burn_after_reading_groups;
|
||||
updates.map(item => {
|
||||
updates.map((item) => {
|
||||
const { gid } = item;
|
||||
const idx = state.autoDeleteMsgChannels.findIndex(tmp => tmp.gid == gid);
|
||||
const idx = state.autoDeleteMsgChannels.findIndex((tmp) => tmp.gid == gid);
|
||||
if (idx !== -1) {
|
||||
// update
|
||||
state.autoDeleteMsgChannels[idx] = item;
|
||||
@@ -169,7 +177,7 @@ const footprintSlice = createSlice({
|
||||
}
|
||||
});
|
||||
},
|
||||
upsertPinChats(state, action: PayloadAction<{ pins: PinChat[], override?: boolean }>) {
|
||||
upsertPinChats(state, action: PayloadAction<{ pins: PinChat[]; override?: boolean }>) {
|
||||
const { pins, override = false } = action.payload;
|
||||
if (override) {
|
||||
state.pinChats = pins;
|
||||
@@ -179,17 +187,20 @@ const footprintSlice = createSlice({
|
||||
},
|
||||
removePinChats(state, action: PayloadAction<PinChatTarget[]>) {
|
||||
const pins = action.payload;
|
||||
state.pinChats = state.pinChats.filter(pin => {
|
||||
state.pinChats = state.pinChats.filter((pin) => {
|
||||
const key = "uid" in pin.target ? "uid" : "gid";
|
||||
// @ts-ignore
|
||||
return !pins.some(p => p[key] == pin.target[key]);
|
||||
return !pins.some((p) => p[key] == pin.target[key]);
|
||||
});
|
||||
},
|
||||
upsertOG(state, action: PayloadAction<{ key: string, value: OG }>) {
|
||||
upsertOG(state, action: PayloadAction<{ key: string; value: OG }>) {
|
||||
const { key, value } = action.payload;
|
||||
state.og[key] = value;
|
||||
},
|
||||
updateHistoryMark(state, action: PayloadAction<{ type: ChatContext, id: number, mid: string, }>) {
|
||||
updateHistoryMark(
|
||||
state,
|
||||
action: PayloadAction<{ type: ChatContext; id: number; mid: string }>
|
||||
) {
|
||||
const { type, id, mid } = action.payload;
|
||||
if (type == "channel") {
|
||||
state.historyChannels[id] = mid;
|
||||
@@ -211,14 +222,14 @@ const footprintSlice = createSlice({
|
||||
state.readChannels[gid] = mid;
|
||||
});
|
||||
},
|
||||
updateChannelVisibleAside(state, action: PayloadAction<{ id: number, aside: ChannelAside }>) {
|
||||
updateChannelVisibleAside(state, action: PayloadAction<{ id: number; aside: ChannelAside }>) {
|
||||
const { id, aside } = action.payload;
|
||||
state.channelAsides[id] = aside;
|
||||
},
|
||||
updateDMVisibleAside(state, action: PayloadAction<{ id: number, aside: DMAside }>) {
|
||||
updateDMVisibleAside(state, action: PayloadAction<{ id: number; aside: DMAside }>) {
|
||||
const { id, aside } = action.payload;
|
||||
state.dmAsides[id] = aside;
|
||||
},
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(resetAuthData, (state) => {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
// import { ContentTypes } from "../config";
|
||||
// import { normalizeFileMessage } from "../../common/utils";
|
||||
// import { ContentType } from "@/types/message";
|
||||
import { Archive } from "@/types/resource";
|
||||
|
||||
export interface State {
|
||||
[key: string]: Archive;
|
||||
}
|
||||
const initialState: State = {
|
||||
};
|
||||
const initialState: State = {};
|
||||
|
||||
const messageArchiveSlice = createSlice({
|
||||
name: "archiveMessage",
|
||||
@@ -19,17 +20,14 @@ const messageArchiveSlice = createSlice({
|
||||
fillArchiveMessage(state, action) {
|
||||
return Object.assign({ ...initialState }, action.payload);
|
||||
},
|
||||
upsertArchiveMessage(state, action: PayloadAction<{ filePath: string, data: Archive }>) {
|
||||
upsertArchiveMessage(state, action: PayloadAction<{ filePath: string; data: Archive }>) {
|
||||
const { filePath, data } = action.payload;
|
||||
state[filePath] = data;
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
resetArchiveMessage,
|
||||
fillArchiveMessage,
|
||||
upsertArchiveMessage
|
||||
} = messageArchiveSlice.actions;
|
||||
export const { resetArchiveMessage, fillArchiveMessage, upsertArchiveMessage } =
|
||||
messageArchiveSlice.actions;
|
||||
|
||||
export default messageArchiveSlice.reducer;
|
||||
|
||||
@@ -27,7 +27,7 @@ const channelMsgSlice = createSlice({
|
||||
const localMsgExisted = state[id]!.findIndex((id) => id == local_id) > -1;
|
||||
if (midExisted || localMsgExisted) return;
|
||||
// 每次入库,都排序
|
||||
const newArr = [...state[id] as number[], +mid].sort((a, b) => a - b);
|
||||
const newArr = [...(state[id] as number[]), +mid].sort((a, b) => a - b);
|
||||
state[id] = newArr;
|
||||
} else {
|
||||
state[id] = [+mid];
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
import { ContentType } from "@/types/message";
|
||||
// import { ContentTypes } from "../config";
|
||||
import { normalizeFileMessage } from "@/utils";
|
||||
import { ContentType } from "@/types/message";
|
||||
|
||||
export interface MessagePayload {
|
||||
mid: number;
|
||||
from_uid?: number;
|
||||
@@ -45,7 +47,7 @@ const messageSlice = createSlice({
|
||||
fillMessage(state, action) {
|
||||
return Object.assign({ ...initialState }, action.payload);
|
||||
},
|
||||
updateMessage(state, action: PayloadAction<{ mid: number;[key: string | number]: any }>) {
|
||||
updateMessage(state, action: PayloadAction<{ mid: number; [key: string | number]: any }>) {
|
||||
const { mid, ...rest } = action.payload;
|
||||
state[mid] = { ...state[mid], ...rest };
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@ const userMsgSlice = createSlice({
|
||||
state.ids.push(+id);
|
||||
}
|
||||
},
|
||||
removeUserMsg(state, action: PayloadAction<{ id: number, mid: number }>) {
|
||||
removeUserMsg(state, action: PayloadAction<{ id: number; mid: number }>) {
|
||||
const { id, mid } = action.payload;
|
||||
if (state.byId[id]) {
|
||||
const idx = state.byId[id].findIndex((i: number) => i == mid);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
import { Server } from "@/types/server";
|
||||
|
||||
export interface StoredServer extends Server {
|
||||
version: string,
|
||||
upgraded: boolean,
|
||||
version: string;
|
||||
upgraded: boolean;
|
||||
logo: string;
|
||||
inviteLink?: {
|
||||
link: string;
|
||||
@@ -49,7 +50,18 @@ const serverSlice = createSlice({
|
||||
contact_verification_enable = false,
|
||||
chat_layout_mode = "Left"
|
||||
} = action.payload || {};
|
||||
return { version, upgraded, name, logo, description, inviteLink, show_user_online_status, webclient_auto_update, contact_verification_enable, chat_layout_mode };
|
||||
return {
|
||||
version,
|
||||
upgraded,
|
||||
name,
|
||||
logo,
|
||||
description,
|
||||
inviteLink,
|
||||
show_user_online_status,
|
||||
webclient_auto_update,
|
||||
contact_verification_enable,
|
||||
chat_layout_mode
|
||||
};
|
||||
},
|
||||
updateInfo(state, action: PayloadAction<Partial<StoredServer>>) {
|
||||
const values = action.payload || {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
export type ListView = "item" | "grid"
|
||||
export type ListView = "item" | "grid";
|
||||
export interface State {
|
||||
online: boolean;
|
||||
ready: boolean;
|
||||
|
||||
+12
-5
@@ -1,15 +1,16 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { isNull, omitBy } from "lodash";
|
||||
import BASE_URL from "../config";
|
||||
import { Contact, ContactStatus } from "@/types/user";
|
||||
|
||||
import { UserLog, UserState } from "@/types/sse";
|
||||
import { Contact, ContactStatus } from "@/types/user";
|
||||
import BASE_URL from "../config";
|
||||
|
||||
type DMAside = "voice" | null;
|
||||
export interface StoredUser extends Contact {
|
||||
online?: boolean;
|
||||
voice?: boolean;
|
||||
avatar?: string;
|
||||
visibleAside?: DMAside
|
||||
visibleAside?: DMAside;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
@@ -99,7 +100,12 @@ const usersSlice = createSlice({
|
||||
}
|
||||
});
|
||||
},
|
||||
updateContactStatus(state, action: PayloadAction<{ uid: number, status: ContactStatus } | { uid: number, status: ContactStatus }[]>) {
|
||||
updateContactStatus(
|
||||
state,
|
||||
action: PayloadAction<
|
||||
{ uid: number; status: ContactStatus } | { uid: number; status: ContactStatus }[]
|
||||
>
|
||||
) {
|
||||
const arr = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||
arr.forEach((data) => {
|
||||
if (state.byId[data.uid]) {
|
||||
@@ -110,5 +116,6 @@ const usersSlice = createSlice({
|
||||
}
|
||||
});
|
||||
|
||||
export const { updateContactStatus, resetUsers, fillUsers, updateUsersByLogs, updateUsersStatus } = usersSlice.actions;
|
||||
export const { updateContactStatus, resetUsers, fillUsers, updateUsersByLogs, updateUsersStatus } =
|
||||
usersSlice.actions;
|
||||
export default usersSlice.reducer;
|
||||
|
||||
+56
-46
@@ -1,53 +1,55 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { KEY_UID } from "../config";
|
||||
import { ConnectionState } from "agora-rtc-sdk-ng";
|
||||
import { resetAuthData } from "./auth.data";
|
||||
|
||||
import { ChatContext } from "@/types/common";
|
||||
import { KEY_UID } from "../config";
|
||||
import { resetAuthData } from "./auth.data";
|
||||
|
||||
export type VoiceBasicInfo = {
|
||||
context: ChatContext,
|
||||
from?: number,
|
||||
id: number,// means to in dm context
|
||||
}
|
||||
context: ChatContext;
|
||||
from?: number;
|
||||
id: number; // means to in dm context
|
||||
};
|
||||
|
||||
export type VoicingInfo = {
|
||||
downlinkNetworkQuality?: number,
|
||||
joining?: boolean,
|
||||
connectionState?: ConnectionState
|
||||
} & VoiceBasicInfo & VoicingMemberInfo
|
||||
downlinkNetworkQuality?: number;
|
||||
joining?: boolean;
|
||||
connectionState?: ConnectionState;
|
||||
} & VoiceBasicInfo &
|
||||
VoicingMemberInfo;
|
||||
|
||||
export type VoicingMemberInfo = {
|
||||
speakingVolume?: number,
|
||||
muted?: boolean,
|
||||
deafen?: boolean,
|
||||
video?: boolean,
|
||||
shareScreen?: boolean
|
||||
}
|
||||
speakingVolume?: number;
|
||||
muted?: boolean;
|
||||
deafen?: boolean;
|
||||
video?: boolean;
|
||||
shareScreen?: boolean;
|
||||
};
|
||||
|
||||
export type VoicingMembers = {
|
||||
ids: number[],
|
||||
ids: number[];
|
||||
byId: {
|
||||
[key: number]: VoicingMemberInfo
|
||||
},
|
||||
pin?: number
|
||||
}
|
||||
[key: number]: VoicingMemberInfo;
|
||||
};
|
||||
pin?: number;
|
||||
};
|
||||
export type VoiceInfo = {
|
||||
channelName: string,
|
||||
memberCount: number
|
||||
} & VoiceBasicInfo
|
||||
channelName: string;
|
||||
memberCount: number;
|
||||
} & VoiceBasicInfo;
|
||||
export type DeviceInfo = Pick<MediaDeviceInfo, "deviceId" | "groupId" | "kind" | "label">;
|
||||
export type MediaDeviceKind = "audioinput" | "audiooutput" | "videoinput"
|
||||
export type MediaDeviceKind = "audioinput" | "audiooutput" | "videoinput";
|
||||
interface State {
|
||||
callingFrom: number,
|
||||
callingTo: number,
|
||||
calling: boolean,
|
||||
voicing: VoicingInfo | null,
|
||||
voicingMembers: VoicingMembers,
|
||||
list: VoiceInfo[],
|
||||
devices: DeviceInfo[],
|
||||
audioInputDeviceId: string,
|
||||
audioOutputDeviceId: string,
|
||||
videoInputDeviceId: string,
|
||||
callingFrom: number;
|
||||
callingTo: number;
|
||||
calling: boolean;
|
||||
voicing: VoicingInfo | null;
|
||||
voicingMembers: VoicingMembers;
|
||||
list: VoiceInfo[];
|
||||
devices: DeviceInfo[];
|
||||
audioInputDeviceId: string;
|
||||
audioOutputDeviceId: string;
|
||||
videoInputDeviceId: string;
|
||||
}
|
||||
const initialState: State = {
|
||||
calling: false,
|
||||
@@ -68,7 +70,10 @@ const voiceSlice = createSlice({
|
||||
name: "voice",
|
||||
initialState,
|
||||
reducers: {
|
||||
updateCallInfo(state, { payload }: PayloadAction<{ from: number, to?: number, calling?: boolean }>) {
|
||||
updateCallInfo(
|
||||
state,
|
||||
{ payload }: PayloadAction<{ from: number; to?: number; calling?: boolean }>
|
||||
) {
|
||||
const { from, to = 0, calling } = payload;
|
||||
state.callingFrom = from;
|
||||
state.callingTo = to;
|
||||
@@ -80,9 +85,9 @@ const voiceSlice = createSlice({
|
||||
state.devices = payload;
|
||||
if (payload.length > 0) {
|
||||
// 默认选择第一个
|
||||
const audioInputDevice = payload.find(v => v.kind == "audioinput");
|
||||
const audioOutputDevice = payload.find(v => v.kind == "audiooutput");
|
||||
const videoInputDevice = payload.find(v => v.kind == "videoinput");
|
||||
const audioInputDevice = payload.find((v) => v.kind == "audioinput");
|
||||
const audioOutputDevice = payload.find((v) => v.kind == "audiooutput");
|
||||
const videoInputDevice = payload.find((v) => v.kind == "videoinput");
|
||||
if (audioInputDevice) {
|
||||
state.audioInputDeviceId = audioInputDevice.deviceId;
|
||||
}
|
||||
@@ -97,7 +102,10 @@ const voiceSlice = createSlice({
|
||||
updateCalling(state, { payload }: PayloadAction<boolean>) {
|
||||
state.calling = payload;
|
||||
},
|
||||
updateSelectDeviceId(state, { payload }: PayloadAction<{ kind: MediaDeviceKind, value: string }>) {
|
||||
updateSelectDeviceId(
|
||||
state,
|
||||
{ payload }: PayloadAction<{ kind: MediaDeviceKind; value: string }>
|
||||
) {
|
||||
const { kind, value } = payload;
|
||||
switch (kind) {
|
||||
case "audioinput":
|
||||
@@ -152,7 +160,6 @@ const voiceSlice = createSlice({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
// reset
|
||||
state.voicing = payload;
|
||||
@@ -177,7 +184,7 @@ const voiceSlice = createSlice({
|
||||
state.list = payload;
|
||||
} else {
|
||||
const { id, context } = payload;
|
||||
const idx = state.list.findIndex(v => v.id == id && v.context == context);
|
||||
const idx = state.list.findIndex((v) => v.id == id && v.context == context);
|
||||
if (idx > -1) {
|
||||
state.list.splice(idx, 1, payload);
|
||||
} else {
|
||||
@@ -203,14 +210,17 @@ const voiceSlice = createSlice({
|
||||
delete state.voicingMembers.byId[payload];
|
||||
}
|
||||
},
|
||||
updateVoicingMember(state, { payload }: PayloadAction<{ uid: number, info: VoicingMemberInfo }>) {
|
||||
updateVoicingMember(
|
||||
state,
|
||||
{ payload }: PayloadAction<{ uid: number; info: VoicingMemberInfo }>
|
||||
) {
|
||||
const idx = state.voicingMembers.ids.findIndex((uid) => uid == payload.uid);
|
||||
if (idx > -1) {
|
||||
const { uid, info } = payload;
|
||||
state.voicingMembers.byId[uid] = { ...state.voicingMembers.byId[uid], ...info };
|
||||
}
|
||||
},
|
||||
updatePin(state, { payload }: PayloadAction<{ uid: number, action: "pin" | "unpin" }>) {
|
||||
updatePin(state, { payload }: PayloadAction<{ uid: number; action: "pin" | "unpin" }>) {
|
||||
const idx = state.voicingMembers.ids.findIndex((uid) => uid == payload.uid);
|
||||
if (idx > -1) {
|
||||
state.voicingMembers.pin = payload.action == "pin" ? payload.uid : undefined;
|
||||
@@ -223,13 +233,13 @@ const voiceSlice = createSlice({
|
||||
if (window.VOICE_CLIENT) {
|
||||
window.VOICE_CLIENT.leave();
|
||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([uid, track]) => {
|
||||
if (track && 'close' in track) {
|
||||
if (track && "close" in track) {
|
||||
track.close();
|
||||
}
|
||||
delete window.VOICE_TRACK_MAP[+uid];
|
||||
});
|
||||
Object.entries(window.VIDEO_TRACK_MAP).forEach(([uid, track]) => {
|
||||
if (track && 'close' in track) {
|
||||
if (track && "close" in track) {
|
||||
track?.close();
|
||||
}
|
||||
delete window.VOICE_TRACK_MAP[+uid];
|
||||
|
||||
+17
-16
@@ -1,26 +1,27 @@
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
||||
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import listenerMiddleware from "./listener.middleware";
|
||||
import authDataReducer from "./slices/auth.data";
|
||||
import voiceReducer from "./slices/voice";
|
||||
import footprintReducer from "./slices/footprint";
|
||||
import serverReducer from "./slices/server";
|
||||
import uiReducer from "./slices/ui";
|
||||
import channelsReducer from "./slices/channels";
|
||||
import usersReducer from "./slices/users";
|
||||
import reactionMsgReducer from "./slices/message.reaction";
|
||||
import channelMsgReducer from "./slices/message.channel";
|
||||
import userMsgReducer from "./slices/message.user";
|
||||
import favoritesReducer from "./slices/favorites";
|
||||
import fileMsgReducer from "./slices/message.file";
|
||||
import archiveMsgReducer from "./slices/message.archive";
|
||||
import messageReducer from "./slices/message";
|
||||
import { authApi } from "./services/auth";
|
||||
import { userApi } from "./services/user";
|
||||
import { channelApi } from "./services/channel";
|
||||
import { messageApi } from "./services/message";
|
||||
import { serverApi } from "./services/server";
|
||||
import { userApi } from "./services/user";
|
||||
import authDataReducer from "./slices/auth.data";
|
||||
import channelsReducer from "./slices/channels";
|
||||
import favoritesReducer from "./slices/favorites";
|
||||
import footprintReducer from "./slices/footprint";
|
||||
import messageReducer from "./slices/message";
|
||||
import archiveMsgReducer from "./slices/message.archive";
|
||||
import channelMsgReducer from "./slices/message.channel";
|
||||
import fileMsgReducer from "./slices/message.file";
|
||||
import reactionMsgReducer from "./slices/message.reaction";
|
||||
import userMsgReducer from "./slices/message.user";
|
||||
import serverReducer from "./slices/server";
|
||||
import uiReducer from "./slices/ui";
|
||||
import usersReducer from "./slices/users";
|
||||
import voiceReducer from "./slices/voice";
|
||||
|
||||
const reducer = combineReducers({
|
||||
authData: authDataReducer,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function useInView<T extends HTMLElement>() {
|
||||
const ref = useRef<T>(null);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
createParagraphPlugin,
|
||||
createSelectOnBackspacePlugin
|
||||
} from "@udecode/plate";
|
||||
|
||||
import { CONFIG } from "./config";
|
||||
|
||||
const basicElements = [
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { initializeApp } from "firebase/app";
|
||||
import { getToken, getMessaging } from "firebase/messaging";
|
||||
import { getMessaging, getToken } from "firebase/messaging";
|
||||
|
||||
import { firebaseConfig, KEY_DEVICE_TOKEN } from "@/app/config";
|
||||
|
||||
let requesting = false;
|
||||
let error = false;
|
||||
const useDeviceToken = (vapidKey: string) => {
|
||||
|
||||
+308
-274
@@ -1,287 +1,321 @@
|
||||
import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { ShareScreenTrack } from '../../types/global';
|
||||
import AudioJoin from '@/assets/join.wav';
|
||||
|
||||
import { useGenerateAgoraTokenMutation } from '../../app/services/server';
|
||||
import { updateChannelVisibleAside, updateDMVisibleAside } from '../../app/slices/footprint';
|
||||
|
||||
import { addVoiceMember, updatePin, updateVoicingInfo, upsertVoiceList } from '../../app/slices/voice';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
import { playAgoraVideo } from '../../utils';
|
||||
import { ChatContext } from '../../types/common';
|
||||
import { useDispatch } from "react-redux";
|
||||
import AgoraRTC, { ICameraVideoTrack, IMicrophoneAudioTrack } from "agora-rtc-sdk-ng";
|
||||
|
||||
import AudioJoin from "@/assets/join.wav";
|
||||
import { useGenerateAgoraTokenMutation } from "../../app/services/server";
|
||||
import { updateChannelVisibleAside, updateDMVisibleAside } from "../../app/slices/footprint";
|
||||
import {
|
||||
addVoiceMember,
|
||||
updatePin,
|
||||
updateVoicingInfo,
|
||||
upsertVoiceList
|
||||
} from "../../app/slices/voice";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import { ChatContext } from "../../types/common";
|
||||
import { ShareScreenTrack } from "../../types/global";
|
||||
import { playAgoraVideo } from "../../utils";
|
||||
|
||||
type VoiceProps = {
|
||||
id: number,
|
||||
context?: ChatContext
|
||||
}
|
||||
id: number;
|
||||
context?: ChatContext;
|
||||
};
|
||||
const audioJoin = new Audio(AudioJoin);
|
||||
const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const { voicingInfo, loginUid, audioInputDevices, audioOutputDevices, videoInputDevices, audioInputDeviceId, audioOutputDeviceId, videoInputDeviceId } = useAppSelector(store => {
|
||||
return {
|
||||
loginUid: store.authData.user?.uid ?? 0,
|
||||
voicingInfo: store.voice.voicing,
|
||||
audioInputDevices: store.voice.devices.filter(d => d.kind == "audioinput") ?? [],
|
||||
audioOutputDevices: store.voice.devices.filter(d => d.kind == "audiooutput") ?? [],
|
||||
videoInputDevices: store.voice.devices.filter(d => d.kind == "videoinput") ?? [],
|
||||
audioInputDeviceId: store.voice.audioInputDeviceId,
|
||||
audioOutputDeviceId: store.voice.audioOutputDeviceId,
|
||||
videoInputDeviceId: store.voice.videoInputDeviceId,
|
||||
};
|
||||
});
|
||||
const [generateToken] = useGenerateAgoraTokenMutation();
|
||||
// const [joining, setJoining] = useState(false);
|
||||
const joinVoice = async () => {
|
||||
// setJoining(true);
|
||||
dispatch(updateVoicingInfo({
|
||||
id,
|
||||
context,
|
||||
joining: true
|
||||
}));
|
||||
const resp = await generateToken(context == "channel" ? { gid: id } : { uid: id });
|
||||
if ('error' in resp) {
|
||||
console.error("generate agora token error");
|
||||
dispatch(updateVoicingInfo({
|
||||
joining: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
} else {
|
||||
console.table(resp.data);
|
||||
const { channel_name, app_id, agora_token, uid } = resp.data;
|
||||
if (window.VOICE_CLIENT) {
|
||||
await window.VOICE_CLIENT.join(app_id, channel_name, agora_token, uid);
|
||||
// Create a local audio track from the microphone audio.
|
||||
const localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack({ microphoneId: audioInputDeviceId });
|
||||
// Publish the local audio track in the channel.
|
||||
await window.VOICE_CLIENT.publish(localAudioTrack);
|
||||
// play the join audio
|
||||
try {
|
||||
await audioJoin.play();
|
||||
} catch (error) {
|
||||
console.warn("play join sound failed!", error);
|
||||
}
|
||||
console.log("Publish success!,joined the channel");
|
||||
dispatch(updateVoicingInfo({
|
||||
deafen: false,
|
||||
muted: false,
|
||||
joining: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
// 把自己加进去
|
||||
dispatch(addVoiceMember(uid));
|
||||
// 放到全局变量里
|
||||
window.VOICE_TRACK_MAP[loginUid] = localAudioTrack;
|
||||
}
|
||||
|
||||
}
|
||||
// setJoining(false);
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
voicingInfo,
|
||||
loginUid,
|
||||
audioInputDevices,
|
||||
audioOutputDevices,
|
||||
videoInputDevices,
|
||||
audioInputDeviceId,
|
||||
audioOutputDeviceId,
|
||||
videoInputDeviceId
|
||||
} = useAppSelector((store) => {
|
||||
return {
|
||||
loginUid: store.authData.user?.uid ?? 0,
|
||||
voicingInfo: store.voice.voicing,
|
||||
audioInputDevices: store.voice.devices.filter((d) => d.kind == "audioinput") ?? [],
|
||||
audioOutputDevices: store.voice.devices.filter((d) => d.kind == "audiooutput") ?? [],
|
||||
videoInputDevices: store.voice.devices.filter((d) => d.kind == "videoinput") ?? [],
|
||||
audioInputDeviceId: store.voice.audioInputDeviceId,
|
||||
audioOutputDeviceId: store.voice.audioOutputDeviceId,
|
||||
videoInputDeviceId: store.voice.videoInputDeviceId
|
||||
};
|
||||
const openCamera = async () => {
|
||||
const localVideoTrack = await AgoraRTC.createCameraVideoTrack({ cameraId: videoInputDeviceId });
|
||||
// 取消正在进行的桌面共享
|
||||
await stopShareScreen();
|
||||
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
||||
dispatch(updateVoicingInfo({
|
||||
video: true,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
// 放到全局变量里
|
||||
window.VIDEO_TRACK_MAP[loginUid] = localVideoTrack;
|
||||
playAgoraVideo(loginUid);
|
||||
};
|
||||
const closeCamera = async () => {
|
||||
const localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ICameraVideoTrack;
|
||||
if (localVideoTrack) {
|
||||
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||
localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
// 关闭视频后,需要把视频的高度设置回去
|
||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||
playerEle.classList.remove("h-[120px]");
|
||||
dispatch(updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
}
|
||||
};
|
||||
const stopShareScreen = async () => {
|
||||
let localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ShareScreenTrack | null;
|
||||
if (!localVideoTrack) return;
|
||||
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||
if ("close" in localVideoTrack) {
|
||||
localVideoTrack.close();
|
||||
} else {
|
||||
localVideoTrack[0].close();
|
||||
// localVideoTrack[0]=null
|
||||
}
|
||||
// localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
// 关闭视频后,需要把视频的高度设置回去
|
||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||
playerEle.classList.remove("h-[120px]");
|
||||
dispatch(updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
};
|
||||
const startShareScreen = async () => {
|
||||
});
|
||||
const [generateToken] = useGenerateAgoraTokenMutation();
|
||||
// const [joining, setJoining] = useState(false);
|
||||
const joinVoice = async () => {
|
||||
// setJoining(true);
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
id,
|
||||
context,
|
||||
joining: true
|
||||
})
|
||||
);
|
||||
const resp = await generateToken(context == "channel" ? { gid: id } : { uid: id });
|
||||
if ("error" in resp) {
|
||||
console.error("generate agora token error");
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
joining: false,
|
||||
id,
|
||||
context
|
||||
})
|
||||
);
|
||||
} else {
|
||||
console.table(resp.data);
|
||||
const { channel_name, app_id, agora_token, uid } = resp.data;
|
||||
if (window.VOICE_CLIENT) {
|
||||
await window.VOICE_CLIENT.join(app_id, channel_name, agora_token, uid);
|
||||
// Create a local audio track from the microphone audio.
|
||||
const localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack({
|
||||
microphoneId: audioInputDeviceId
|
||||
});
|
||||
// Publish the local audio track in the channel.
|
||||
await window.VOICE_CLIENT.publish(localAudioTrack);
|
||||
// play the join audio
|
||||
try {
|
||||
const localVideoTrack = await AgoraRTC.createScreenVideoTrack({
|
||||
// electronScreenSourceId: "share_screen",
|
||||
selfBrowserSurface: "exclude",
|
||||
// 配置屏幕共享编码参数,详情请查看 API 文档。
|
||||
encoderConfig: "1080p_1",
|
||||
// 设置视频传输优化策略为清晰优先或流畅优先。
|
||||
optimizationMode: "detail",
|
||||
});
|
||||
|
||||
// 取消正在进行的视频
|
||||
await closeCamera();
|
||||
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
||||
dispatch(updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: true,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
// 放到全局变量里
|
||||
window.VIDEO_TRACK_MAP[loginUid] = localVideoTrack;
|
||||
playAgoraVideo(loginUid);
|
||||
// 进入全屏并Pin自己
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
} else {
|
||||
dispatch(updateDMVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
}
|
||||
dispatch(updatePin({ uid: loginUid, action: "pin" }));
|
||||
// 监听屏幕共享结束事件
|
||||
if ("close" in localVideoTrack) {
|
||||
localVideoTrack.getMediaStreamTrack().onended = () => {
|
||||
stopShareScreen();
|
||||
};
|
||||
}
|
||||
await audioJoin.play();
|
||||
} catch (error) {
|
||||
console.log("start share screen error", error);
|
||||
console.warn("play join sound failed!", error);
|
||||
}
|
||||
};
|
||||
|
||||
const leave = async () => {
|
||||
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||
const localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ICameraVideoTrack;
|
||||
if (window.VOICE_CLIENT) {
|
||||
await window.VOICE_CLIENT.leave();
|
||||
dispatch(updateVoicingInfo(null));
|
||||
if (localAudioTrack) {
|
||||
localAudioTrack.close();
|
||||
window.VOICE_TRACK_MAP[loginUid] = null;
|
||||
}
|
||||
if (localVideoTrack) {
|
||||
localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
}
|
||||
const updateAside = context == "channel" ? updateChannelVisibleAside : updateDMVisibleAside;
|
||||
dispatch(updateAside({ id, aside: null }));
|
||||
// 即时更新对应的活跃列表信息
|
||||
dispatch(upsertVoiceList({
|
||||
id,
|
||||
context,
|
||||
memberCount: 0,
|
||||
// will fix it
|
||||
channelName: `vocechat:${context}:${id}`,
|
||||
}));
|
||||
}
|
||||
};
|
||||
const setMute = (mute: boolean) => {
|
||||
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||
if (!localAudioTrack) return;
|
||||
localAudioTrack.setMuted(mute);
|
||||
if (mute == false && voicingInfo?.deafen) {
|
||||
// 远端音频,恢复原音
|
||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||
audioTrack?.setVolume(100);
|
||||
});
|
||||
dispatch(updateVoicingInfo({
|
||||
muted: false,
|
||||
id,
|
||||
context
|
||||
}));
|
||||
}
|
||||
dispatch(updateVoicingInfo({
|
||||
muted: mute,
|
||||
console.log("Publish success!,joined the channel");
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
deafen: false,
|
||||
muted: false,
|
||||
joining: false,
|
||||
id,
|
||||
context
|
||||
}));
|
||||
};
|
||||
const setDeafen = (deafen: boolean) => {
|
||||
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||
if (!localAudioTrack) return;
|
||||
if (deafen) {
|
||||
localAudioTrack.setMuted(true);
|
||||
// 远端音频,全部静音
|
||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||
audioTrack?.setVolume(0);
|
||||
});
|
||||
} else {
|
||||
localAudioTrack.setMuted(false);
|
||||
// 远端音频,恢复原音
|
||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||
audioTrack?.setVolume(100);
|
||||
});
|
||||
}
|
||||
dispatch(updateVoicingInfo({ deafen, id, context }));
|
||||
};
|
||||
const enterFullscreen = (uid?: number) => {
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
} else {
|
||||
dispatch(updateDMVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
}
|
||||
if (uid) {
|
||||
dispatch(updatePin({ uid, action: "pin" }));
|
||||
}
|
||||
};
|
||||
const exitFullscreen = () => {
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice" }));
|
||||
} else {
|
||||
dispatch(updateDMVisibleAside({ id, aside: null }));
|
||||
}
|
||||
};
|
||||
const joinedAtThisContext = voicingInfo ? (voicingInfo.id == id && voicingInfo.context == context) : false;
|
||||
return {
|
||||
setMute,
|
||||
setDeafen,
|
||||
leave,
|
||||
// canVoice,
|
||||
voicingInfo,
|
||||
joining: voicingInfo ? voicingInfo.joining : undefined,
|
||||
joinedAtThisContext,
|
||||
joined: !!voicingInfo,
|
||||
joinVoice,
|
||||
openCamera,
|
||||
closeCamera,
|
||||
startShareScreen,
|
||||
stopShareScreen,
|
||||
enterFullscreen,
|
||||
exitFullscreen,
|
||||
audioInputDevices,
|
||||
audioOutputDevices,
|
||||
videoInputDevices,
|
||||
audioInputDeviceId,
|
||||
audioOutputDeviceId,
|
||||
videoInputDeviceId
|
||||
};
|
||||
})
|
||||
);
|
||||
// 把自己加进去
|
||||
dispatch(addVoiceMember(uid));
|
||||
// 放到全局变量里
|
||||
window.VOICE_TRACK_MAP[loginUid] = localAudioTrack;
|
||||
}
|
||||
}
|
||||
// setJoining(false);
|
||||
};
|
||||
const openCamera = async () => {
|
||||
const localVideoTrack = await AgoraRTC.createCameraVideoTrack({ cameraId: videoInputDeviceId });
|
||||
// 取消正在进行的桌面共享
|
||||
await stopShareScreen();
|
||||
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
video: true,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context
|
||||
})
|
||||
);
|
||||
// 放到全局变量里
|
||||
window.VIDEO_TRACK_MAP[loginUid] = localVideoTrack;
|
||||
playAgoraVideo(loginUid);
|
||||
};
|
||||
const closeCamera = async () => {
|
||||
const localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ICameraVideoTrack;
|
||||
if (localVideoTrack) {
|
||||
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||
localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
// 关闭视频后,需要把视频的高度设置回去
|
||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||
playerEle.classList.remove("h-[120px]");
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
const stopShareScreen = async () => {
|
||||
let localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ShareScreenTrack | null;
|
||||
if (!localVideoTrack) return;
|
||||
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||
if ("close" in localVideoTrack) {
|
||||
localVideoTrack.close();
|
||||
} else {
|
||||
localVideoTrack[0].close();
|
||||
// localVideoTrack[0]=null
|
||||
}
|
||||
// localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
// 关闭视频后,需要把视频的高度设置回去
|
||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||
playerEle.classList.remove("h-[120px]");
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context
|
||||
})
|
||||
);
|
||||
};
|
||||
const startShareScreen = async () => {
|
||||
try {
|
||||
const localVideoTrack = await AgoraRTC.createScreenVideoTrack({
|
||||
// electronScreenSourceId: "share_screen",
|
||||
selfBrowserSurface: "exclude",
|
||||
// 配置屏幕共享编码参数,详情请查看 API 文档。
|
||||
encoderConfig: "1080p_1",
|
||||
// 设置视频传输优化策略为清晰优先或流畅优先。
|
||||
optimizationMode: "detail"
|
||||
});
|
||||
|
||||
// 取消正在进行的视频
|
||||
await closeCamera();
|
||||
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: true,
|
||||
id,
|
||||
context
|
||||
})
|
||||
);
|
||||
// 放到全局变量里
|
||||
window.VIDEO_TRACK_MAP[loginUid] = localVideoTrack;
|
||||
playAgoraVideo(loginUid);
|
||||
// 进入全屏并Pin自己
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
} else {
|
||||
dispatch(updateDMVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
}
|
||||
dispatch(updatePin({ uid: loginUid, action: "pin" }));
|
||||
// 监听屏幕共享结束事件
|
||||
if ("close" in localVideoTrack) {
|
||||
localVideoTrack.getMediaStreamTrack().onended = () => {
|
||||
stopShareScreen();
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("start share screen error", error);
|
||||
}
|
||||
};
|
||||
|
||||
const leave = async () => {
|
||||
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||
const localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ICameraVideoTrack;
|
||||
if (window.VOICE_CLIENT) {
|
||||
await window.VOICE_CLIENT.leave();
|
||||
dispatch(updateVoicingInfo(null));
|
||||
if (localAudioTrack) {
|
||||
localAudioTrack.close();
|
||||
window.VOICE_TRACK_MAP[loginUid] = null;
|
||||
}
|
||||
if (localVideoTrack) {
|
||||
localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
}
|
||||
const updateAside = context == "channel" ? updateChannelVisibleAside : updateDMVisibleAside;
|
||||
dispatch(updateAside({ id, aside: null }));
|
||||
// 即时更新对应的活跃列表信息
|
||||
dispatch(
|
||||
upsertVoiceList({
|
||||
id,
|
||||
context,
|
||||
memberCount: 0,
|
||||
// will fix it
|
||||
channelName: `vocechat:${context}:${id}`
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
const setMute = (mute: boolean) => {
|
||||
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||
if (!localAudioTrack) return;
|
||||
localAudioTrack.setMuted(mute);
|
||||
if (mute == false && voicingInfo?.deafen) {
|
||||
// 远端音频,恢复原音
|
||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||
audioTrack?.setVolume(100);
|
||||
});
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
muted: false,
|
||||
id,
|
||||
context
|
||||
})
|
||||
);
|
||||
}
|
||||
dispatch(
|
||||
updateVoicingInfo({
|
||||
muted: mute,
|
||||
id,
|
||||
context
|
||||
})
|
||||
);
|
||||
};
|
||||
const setDeafen = (deafen: boolean) => {
|
||||
const localAudioTrack = window.VOICE_TRACK_MAP[loginUid] as IMicrophoneAudioTrack;
|
||||
if (!localAudioTrack) return;
|
||||
if (deafen) {
|
||||
localAudioTrack.setMuted(true);
|
||||
// 远端音频,全部静音
|
||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||
audioTrack?.setVolume(0);
|
||||
});
|
||||
} else {
|
||||
localAudioTrack.setMuted(false);
|
||||
// 远端音频,恢复原音
|
||||
Object.entries(window.VOICE_TRACK_MAP).forEach(([, audioTrack]) => {
|
||||
audioTrack?.setVolume(100);
|
||||
});
|
||||
}
|
||||
dispatch(updateVoicingInfo({ deafen, id, context }));
|
||||
};
|
||||
const enterFullscreen = (uid?: number) => {
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
} else {
|
||||
dispatch(updateDMVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
}
|
||||
if (uid) {
|
||||
dispatch(updatePin({ uid, action: "pin" }));
|
||||
}
|
||||
};
|
||||
const exitFullscreen = () => {
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice" }));
|
||||
} else {
|
||||
dispatch(updateDMVisibleAside({ id, aside: null }));
|
||||
}
|
||||
};
|
||||
const joinedAtThisContext = voicingInfo
|
||||
? voicingInfo.id == id && voicingInfo.context == context
|
||||
: false;
|
||||
return {
|
||||
setMute,
|
||||
setDeafen,
|
||||
leave,
|
||||
// canVoice,
|
||||
voicingInfo,
|
||||
joining: voicingInfo ? voicingInfo.joining : undefined,
|
||||
joinedAtThisContext,
|
||||
joined: !!voicingInfo,
|
||||
joinVoice,
|
||||
openCamera,
|
||||
closeCamera,
|
||||
startShareScreen,
|
||||
stopShareScreen,
|
||||
enterFullscreen,
|
||||
exitFullscreen,
|
||||
audioInputDevices,
|
||||
audioOutputDevices,
|
||||
videoInputDevices,
|
||||
audioInputDeviceId,
|
||||
audioOutputDeviceId,
|
||||
videoInputDeviceId
|
||||
};
|
||||
};
|
||||
|
||||
export default useVoice;
|
||||
export default useVoice;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { addMessage, MessagePayload } from "@/app/slices/message";
|
||||
import { addChannelMsg } from "@/app/slices/message.channel";
|
||||
import { addUserMsg } from "@/app/slices/message.user";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
// import { useDebounce } from "rooks";
|
||||
|
||||
function useChatScroll<T extends HTMLElement>() {
|
||||
|
||||
+17
-11
@@ -1,18 +1,20 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isEqual } from "lodash";
|
||||
|
||||
import {
|
||||
useUpdateLoginConfigMutation,
|
||||
useUpdateSMTPConfigMutation,
|
||||
useLazyGetAgoraConfigQuery,
|
||||
useLazyGetFirebaseConfigQuery,
|
||||
useLazyGetLoginConfigQuery,
|
||||
useLazyGetSMTPConfigQuery,
|
||||
useUpdateAgoraConfigMutation,
|
||||
useUpdateFirebaseConfigMutation,
|
||||
useLazyGetFirebaseConfigQuery,
|
||||
useLazyGetAgoraConfigQuery,
|
||||
useLazyGetSMTPConfigQuery,
|
||||
useLazyGetLoginConfigQuery
|
||||
useUpdateLoginConfigMutation,
|
||||
useUpdateSMTPConfigMutation
|
||||
} from "@/app/services/server";
|
||||
import { AgoraConfig, FirebaseConfig, LoginConfig, SMTPConfig } from "@/types/server";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// config: smtp agora login firebase
|
||||
type ConfigType = "smtp" | "agora" | "login" | "firebase";
|
||||
type ConfigMap = Record<ConfigType, AgoraConfig | FirebaseConfig | LoginConfig | SMTPConfig>;
|
||||
@@ -23,10 +25,14 @@ export default function useConfig(config: keyof ConfigMap = "smtp") {
|
||||
const { t: ct } = useTranslation();
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [values, setValues] = useState<valuesOf<ConfigMap> | undefined>(undefined);
|
||||
const [updateLoginConfig, { isSuccess: LoginUpdated, isLoading: LoginUpdating }] = useUpdateLoginConfigMutation();
|
||||
const [updateSMTPConfig, { isSuccess: SMTPUpdated, isLoading: SMTPUpdating }] = useUpdateSMTPConfigMutation();
|
||||
const [updateAgoraConfig, { isSuccess: AgoraUpdated, isLoading: AgoraUpdating }] = useUpdateAgoraConfigMutation();
|
||||
const [updateFirebaseConfig, { isSuccess: FirebaseUpdated, isLoading: FirebaseUpdating }] = useUpdateFirebaseConfigMutation();
|
||||
const [updateLoginConfig, { isSuccess: LoginUpdated, isLoading: LoginUpdating }] =
|
||||
useUpdateLoginConfigMutation();
|
||||
const [updateSMTPConfig, { isSuccess: SMTPUpdated, isLoading: SMTPUpdating }] =
|
||||
useUpdateSMTPConfigMutation();
|
||||
const [updateAgoraConfig, { isSuccess: AgoraUpdated, isLoading: AgoraUpdating }] =
|
||||
useUpdateAgoraConfigMutation();
|
||||
const [updateFirebaseConfig, { isSuccess: FirebaseUpdated, isLoading: FirebaseUpdating }] =
|
||||
useUpdateFirebaseConfigMutation();
|
||||
const [getAgoraConfig, { data: agoraConfig }] = useLazyGetAgoraConfigQuery();
|
||||
const [getLoginConfig, { data: loginConfig }] = useLazyGetLoginConfigQuery();
|
||||
const [getSMTPConfig, { data: smtpConfig }] = useLazyGetSMTPConfigQuery();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { copyImageToClipboard } from "copy-image-clipboard";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { copyImageToClipboard } from "copy-image-clipboard";
|
||||
|
||||
const useCopy = (config: { enableToast: boolean } | void) => {
|
||||
const { enableToast = true } = config || {};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { Channel } from "@/types/channel";
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useSendChannelMsgMutation } from "@/app/services/channel";
|
||||
import { useSendMsgMutation } from "@/app/services/user";
|
||||
import { useCreateArchiveMutation } from "@/app/services/message";
|
||||
import { useSendMsgMutation } from "@/app/services/user";
|
||||
|
||||
export default function useForwardMessage() {
|
||||
const [forwarding, setForwarding] = useState(false);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
useGetGoogleAuthConfigQuery,
|
||||
useUpdateGoogleAuthConfigMutation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useUpdateChannelMutation, useLazyLeaveChannelQuery } from "@/app/services/channel";
|
||||
import { useLazyLeaveChannelQuery, useUpdateChannelMutation } from "@/app/services/channel";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
|
||||
export default function useLeaveChannel(cid: number) {
|
||||
|
||||
+14
-3
@@ -1,23 +1,34 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import {
|
||||
useCheckLicenseMutation,
|
||||
useGetLicenseQuery,
|
||||
useUpsertLicenseMutation
|
||||
} from "@/app/services/server";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
|
||||
// type Props = {
|
||||
// refetchOnMountOrArgChange?: boolean
|
||||
// } | undefined
|
||||
const useLicense = (refetchOnMountOrArgChange = false) => {
|
||||
const { userCount, isGuest, upgraded } = useAppSelector((store) => {
|
||||
return { userCount: store.users.ids.length, isGuest: store.authData.guest, upgraded: store.server.upgraded };
|
||||
return {
|
||||
userCount: store.users.ids.length,
|
||||
isGuest: store.authData.guest,
|
||||
upgraded: store.server.upgraded
|
||||
};
|
||||
});
|
||||
const { data: license, refetch: refetchLicense, isLoading } = useGetLicenseQuery(undefined, {
|
||||
const {
|
||||
data: license,
|
||||
refetch: refetchLicense,
|
||||
isLoading
|
||||
} = useGetLicenseQuery(undefined, {
|
||||
refetchOnMountOrArgChange,
|
||||
skip: isGuest
|
||||
});
|
||||
const [check, { isLoading: isChecking, isSuccess: checked }] = useCheckLicenseMutation();
|
||||
const [upsert, { isSuccess: upserted, isLoading: upserting, reset: resetUpsert }] = useUpsertLicenseMutation();
|
||||
const [upsert, { isSuccess: upserted, isLoading: upserting, reset: resetUpsert }] =
|
||||
useUpsertLicenseMutation();
|
||||
const checkLicense = (l: string) => {
|
||||
check(l);
|
||||
};
|
||||
|
||||
+10
-8
@@ -1,13 +1,15 @@
|
||||
import { useDispatch } from "react-redux";
|
||||
import { resetFootprint } from "@/app/slices/footprint";
|
||||
import { resetChannels } from "@/app/slices/channels";
|
||||
import { resetUsers } from "@/app/slices/users";
|
||||
import { resetChannelMsg } from "@/app/slices/message.channel";
|
||||
import { resetUserMsg } from "@/app/slices/message.user";
|
||||
import { resetReactionMessage } from "@/app/slices/message.reaction";
|
||||
import { resetFileMessage } from "@/app/slices/message.file";
|
||||
import { resetMessage } from "@/app/slices/message";
|
||||
|
||||
import { useLazyLogoutQuery } from "@/app/services/auth";
|
||||
import { resetChannels } from "@/app/slices/channels";
|
||||
import { resetFootprint } from "@/app/slices/footprint";
|
||||
import { resetMessage } from "@/app/slices/message";
|
||||
import { resetChannelMsg } from "@/app/slices/message.channel";
|
||||
import { resetFileMessage } from "@/app/slices/message.file";
|
||||
import { resetReactionMessage } from "@/app/slices/message.reaction";
|
||||
import { resetUserMsg } from "@/app/slices/message.user";
|
||||
import { resetUsers } from "@/app/slices/users";
|
||||
|
||||
export default function useLogout() {
|
||||
const dispatch = useDispatch();
|
||||
const [logout, { isLoading, isSuccess }] = useLazyLogoutQuery();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { normalizeArchiveData } from "../utils";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useLazyGetArchiveMessageQuery } from "@/app/services/message";
|
||||
import { ArchiveMessage } from "@/types/resource";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { ArchiveMessage } from "@/types/resource";
|
||||
import { normalizeArchiveData } from "../utils";
|
||||
|
||||
export default function useNormalizeMessage(filePath: string | null) {
|
||||
const archiveData = useAppSelector(store => store.archiveMessage[filePath ?? ""]);
|
||||
const archiveData = useAppSelector((store) => store.archiveMessage[filePath ?? ""]);
|
||||
const [normalizedMessages, setNormalizedMessages] = useState<ArchiveMessage[] | null>(null);
|
||||
const [getArchiveMessage, { isError, isLoading, isSuccess }] =
|
||||
useLazyGetArchiveMessageQuery();
|
||||
const [getArchiveMessage, { isError, isLoading, isSuccess }] = useLazyGetArchiveMessageQuery();
|
||||
useEffect(() => {
|
||||
if (archiveData) {
|
||||
const msgs = normalizeArchiveData(archiveData, filePath);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { KEY_PWA_INSTALLED } from "@/app/config";
|
||||
import { BeforeInstallPromptEvent } from "@/types/global";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { usePinMessageMutation, useUnpinMessageMutation } from "@/app/services/message";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { PinnedMessage } from "@/types/channel";
|
||||
|
||||
+34
-12
@@ -1,14 +1,19 @@
|
||||
import { useEffect } from "react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import initCache, { useRehydrate } from "@/app/cache";
|
||||
import { useLazyGetFavoritesQuery } from "@/app/services/message";
|
||||
import { useLazyGetFavoritesQuery, useLazyLoadMoreMessagesQuery } from "@/app/services/message";
|
||||
import {
|
||||
useLazyGetServerQuery,
|
||||
useLazyGetServerVersionQuery,
|
||||
useLazyGetSystemCommonQuery
|
||||
} from "@/app/services/server";
|
||||
import { useLazyGetContactsQuery, useLazyGetUsersQuery } from "@/app/services/user";
|
||||
import { useLazyGetSystemCommonQuery, useLazyGetServerQuery, useLazyGetServerVersionQuery } from "@/app/services/server";
|
||||
import useStreaming from "./useStreaming";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { useLazyLoadMoreMessagesQuery } from "@/app/services/message";
|
||||
import useLicense from "./useLicense";
|
||||
import { compareVersion } from "../utils";
|
||||
import useLicense from "./useLicense";
|
||||
import useStreaming from "./useStreaming";
|
||||
|
||||
// type Props={
|
||||
// guest?:boolean
|
||||
// }
|
||||
@@ -44,16 +49,27 @@ export default function usePreload() {
|
||||
data: favorites
|
||||
}
|
||||
] = useLazyGetFavoritesQuery();
|
||||
const [getUsers, { isLoading: usersLoading, isSuccess: usersSuccess, isError: usersError, data: users }] = useLazyGetUsersQuery();
|
||||
const [
|
||||
getUsers,
|
||||
{ isLoading: usersLoading, isSuccess: usersSuccess, isError: usersError, data: users }
|
||||
] = useLazyGetUsersQuery();
|
||||
const [
|
||||
getContacts,
|
||||
{ isLoading: contactsLoading, isSuccess: contactsSuccess, isError: contactsError, data: contacts }
|
||||
{
|
||||
isLoading: contactsLoading,
|
||||
isSuccess: contactsSuccess,
|
||||
isError: contactsError,
|
||||
data: contacts
|
||||
}
|
||||
] = useLazyGetContactsQuery();
|
||||
const [
|
||||
getServer,
|
||||
{ isLoading: serverLoading, isSuccess: serverSuccess, isError: serverError, data: server }
|
||||
] = useLazyGetServerQuery();
|
||||
const [getServerVersion, { data: serverVersion, isSuccess: serverVersionSuccess, isLoading: loadingServerVersion }] = useLazyGetServerVersionQuery();
|
||||
const [
|
||||
getServerVersion,
|
||||
{ data: serverVersion, isSuccess: serverVersionSuccess, isLoading: loadingServerVersion }
|
||||
] = useLazyGetServerVersionQuery();
|
||||
const [getSystemCommon] = useLazyGetSystemCommonQuery();
|
||||
useEffect(() => {
|
||||
initCache();
|
||||
@@ -79,14 +95,14 @@ export default function usePreload() {
|
||||
useEffect(() => {
|
||||
if (rehydrated && serverVersion) {
|
||||
getUsers().then(() => {
|
||||
if (compareVersion(serverVersion, '0.3.7') >= 0 && !isGuest) {
|
||||
if (compareVersion(serverVersion, "0.3.7") >= 0 && !isGuest) {
|
||||
// 根据版本号判断是否需要初始化contact
|
||||
getContacts();
|
||||
}
|
||||
});
|
||||
getServer();
|
||||
getFavorites();
|
||||
if (compareVersion(serverVersion, '0.3.4') >= 0) {
|
||||
if (compareVersion(serverVersion, "0.3.4") >= 0) {
|
||||
// 根据版本号判断是否需要调用system common api
|
||||
getSystemCommon();
|
||||
}
|
||||
@@ -99,9 +115,15 @@ export default function usePreload() {
|
||||
useEffect(() => {
|
||||
setStreamingReady(canStreaming);
|
||||
}, [canStreaming]);
|
||||
const enableContact = serverVersion ? compareVersion(serverVersion, '0.3.7') >= 0 : false;
|
||||
const enableContact = serverVersion ? compareVersion(serverVersion, "0.3.7") >= 0 : false;
|
||||
return {
|
||||
loading: usersLoading || serverLoading || favoritesLoading || !rehydrated || loadingLicense || loadingServerVersion,
|
||||
loading:
|
||||
usersLoading ||
|
||||
serverLoading ||
|
||||
favoritesLoading ||
|
||||
!rehydrated ||
|
||||
loadingLicense ||
|
||||
loadingServerVersion,
|
||||
error: usersError && serverError && favoritesError,
|
||||
success: usersSuccess && serverSuccess && favoritesSuccess && serverVersionSuccess,
|
||||
data: {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import toast from "react-hot-toast";
|
||||
import { removeReplyingMessage, addReplyingMessage, MessagePayload } from "@/app/slices/message";
|
||||
|
||||
import { useSendChannelMsgMutation } from "@/app/services/channel";
|
||||
import { useSendMsgMutation } from "@/app/services/user";
|
||||
import { useReplyMessageMutation } from "@/app/services/message";
|
||||
import { useSendMsgMutation } from "@/app/services/user";
|
||||
import { addReplyingMessage, MessagePayload, removeReplyingMessage } from "@/app/slices/message";
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store";
|
||||
import { ContentTypeKey } from "@/types/message";
|
||||
import { ChatContext } from "@/types/common";
|
||||
import { ContentTypeKey } from "@/types/message";
|
||||
|
||||
interface Props {
|
||||
context: ChatContext;
|
||||
@@ -20,7 +21,9 @@ interface SendMessagesDTO {
|
||||
channels: number[];
|
||||
}
|
||||
|
||||
type SendMessageDTO = { type: ContentTypeKey } & Partial<MessagePayload> & { ignoreLocal?: boolean }
|
||||
type SendMessageDTO = { type: ContentTypeKey } & Partial<MessagePayload> & {
|
||||
ignoreLocal?: boolean;
|
||||
};
|
||||
const useSendMessage = (props?: Props) => {
|
||||
const { context = "dm", from = 0, to = 0 } = props || {};
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { batch } from "react-redux";
|
||||
import { addChannelMsg, removeChannelMsg } from "@/app/slices/message.channel";
|
||||
import { addMessage, removeMessage, updateMessage } from "@/app/slices/message";
|
||||
import { toggleReactionMessage } from "@/app/slices/message.reaction";
|
||||
import { addFileMessage, removeFileMessage } from "@/app/slices/message.file";
|
||||
import { addUserMsg, removeUserMsg } from "@/app/slices/message.user";
|
||||
import { updateAfterMid } from "@/app/slices/footprint";
|
||||
|
||||
import { ContentTypes } from "@/app/config";
|
||||
import { ChatEvent } from "@/types/sse";
|
||||
import { updateAfterMid } from "@/app/slices/footprint";
|
||||
import { addMessage, removeMessage, updateMessage } from "@/app/slices/message";
|
||||
import { addChannelMsg, removeChannelMsg } from "@/app/slices/message.channel";
|
||||
import { addFileMessage, removeFileMessage } from "@/app/slices/message.file";
|
||||
import { toggleReactionMessage } from "@/app/slices/message.reaction";
|
||||
import { addUserMsg, removeUserMsg } from "@/app/slices/message.user";
|
||||
import { AppDispatch } from "@/app/store";
|
||||
import { ChatEvent } from "@/types/sse";
|
||||
|
||||
type CurrentState = {
|
||||
afterMid: number,
|
||||
afterMid: number;
|
||||
ready: boolean;
|
||||
loginUid: number;
|
||||
readUsers: {
|
||||
@@ -20,7 +21,12 @@ type CurrentState = {
|
||||
[key: number]: number;
|
||||
};
|
||||
};
|
||||
const handler = (data: ChatEvent, dispatch: AppDispatch, currState: CurrentState, fromHistory = false) => {
|
||||
const handler = (
|
||||
data: ChatEvent,
|
||||
dispatch: AppDispatch,
|
||||
currState: CurrentState,
|
||||
fromHistory = false
|
||||
) => {
|
||||
const {
|
||||
mid,
|
||||
from_uid,
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { BroadcastChannel } from 'broadcast-channel';
|
||||
import { useEffect, useState } from "react";
|
||||
import { BroadcastChannel } from "broadcast-channel";
|
||||
|
||||
// import useStreaming from './useStreaming';
|
||||
|
||||
type ChannelData = {
|
||||
type: "NEW_TAB",
|
||||
message: string
|
||||
}
|
||||
type: "NEW_TAB";
|
||||
message: string;
|
||||
};
|
||||
const useTabBroadcast = () => {
|
||||
const [tabActive, setTabActive] = useState(true);
|
||||
useEffect(() => {
|
||||
const channel = new BroadcastChannel('VOCECHAT_CHANNEL');
|
||||
channel.postMessage({ type: "NEW_TAB", message: "new tab opened" } as ChannelData);
|
||||
const handler = (data: ChannelData) => {
|
||||
console.log("channel data", data);
|
||||
if (data.type == "NEW_TAB") {
|
||||
setTabActive(false);
|
||||
}
|
||||
};
|
||||
channel.addEventListener('message', handler);
|
||||
return () => {
|
||||
channel.removeEventListener("message", handler);
|
||||
channel.close();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
tabActive
|
||||
const [tabActive, setTabActive] = useState(true);
|
||||
useEffect(() => {
|
||||
const channel = new BroadcastChannel("VOCECHAT_CHANNEL");
|
||||
channel.postMessage({ type: "NEW_TAB", message: "new tab opened" } as ChannelData);
|
||||
const handler = (data: ChannelData) => {
|
||||
console.log("channel data", data);
|
||||
if (data.type == "NEW_TAB") {
|
||||
setTabActive(false);
|
||||
}
|
||||
};
|
||||
channel.addEventListener("message", handler);
|
||||
return () => {
|
||||
channel.removeEventListener("message", handler);
|
||||
channel.close();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
tabActive
|
||||
};
|
||||
};
|
||||
|
||||
export default useTabBroadcast;
|
||||
export default useTabBroadcast;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { updateUploadFiles } from "@/app/slices/ui";
|
||||
|
||||
import BASE_URL, { FILE_SLICE_SIZE } from "@/app/config";
|
||||
import { usePrepareUploadFileMutation, useUploadFileMutation } from "@/app/services/message";
|
||||
import { updateUploadFiles } from "@/app/slices/ui";
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store";
|
||||
import { Message } from "@/types/channel";
|
||||
import { UploadFileResponse } from "@/types/message";
|
||||
import { ChatContext } from "@/types/common";
|
||||
import { UploadFileResponse } from "@/types/message";
|
||||
|
||||
interface IProps {
|
||||
context: ChatContext;
|
||||
@@ -25,7 +26,8 @@ const useUploadFile = (props?: IProps) => {
|
||||
const canceledRef = useRef(false);
|
||||
const sliceUploadedCountRef = useRef(0);
|
||||
const totalSliceCountRef = useRef(1);
|
||||
const [prepareUploadFile, { isLoading: isPreparing, isError: prepareFileError }] = usePrepareUploadFileMutation();
|
||||
const [prepareUploadFile, { isLoading: isPreparing, isError: prepareFileError }] =
|
||||
usePrepareUploadFileMutation();
|
||||
const [uploadFileFn, { isLoading: isUploading, isError: uploadFileError }] =
|
||||
useUploadFileMutation();
|
||||
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
// import { ContentTypes } from "@/app/config";
|
||||
import { useNavigate, useMatch } from "react-router-dom";
|
||||
import { hideAll } from "tippy.js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
// import { ContentTypes } from "@/app/config";
|
||||
import { useMatch, useNavigate } from "react-router-dom";
|
||||
import { hideAll } from "tippy.js";
|
||||
|
||||
import { useRemoveMembersMutation } from "@/app/services/channel";
|
||||
import { useLazyDeleteUserQuery, useUpdateContactStatusMutation } from "@/app/services/user";
|
||||
import useCopy from "./useCopy";
|
||||
import { updateDMVisibleAside } from "@/app/slices/footprint";
|
||||
import { updateCallInfo } from "@/app/slices/voice";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { useVoice } from "@/components/Voice";
|
||||
import { updateCallInfo } from "@/app/slices/voice";
|
||||
import { updateDMVisibleAside } from "@/app/slices/footprint";
|
||||
import { useDispatch } from "react-redux";
|
||||
import useCopy from "./useCopy";
|
||||
|
||||
interface IProps {
|
||||
uid?: number;
|
||||
cid?: number;
|
||||
}
|
||||
const useUserOperation = ({ uid, cid }: IProps) => {
|
||||
const { joinVoice, joined, joining = false, joinedAtThisContext } = useVoice({ id: uid ?? 0, context: "dm" });
|
||||
const {
|
||||
joinVoice,
|
||||
joined,
|
||||
joining = false,
|
||||
joinedAtThisContext
|
||||
} = useVoice({ id: uid ?? 0, context: "dm" });
|
||||
const dispatch = useDispatch();
|
||||
const { t: ct } = useTranslation();
|
||||
const [passedUid, setPassedUid] = useState<number | undefined>(undefined);
|
||||
|
||||
+12
-10
@@ -1,18 +1,20 @@
|
||||
import i18n from "i18next";
|
||||
import dayjs from "dayjs";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import Backend, { HttpBackendOptions } from "i18next-http-backend";
|
||||
import dayjs from "dayjs";
|
||||
import i18n from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import common from "../public/locales/en/common.json";
|
||||
import Backend, { HttpBackendOptions } from "i18next-http-backend";
|
||||
|
||||
import pkg from "../package.json";
|
||||
import auth from "../public/locales/en/auth.json";
|
||||
import member from "../public/locales/en/member.json";
|
||||
import chat from "../public/locales/en/chat.json";
|
||||
import common from "../public/locales/en/common.json";
|
||||
import fav from "../public/locales/en/fav.json";
|
||||
import widget from "../public/locales/en/widget.json";
|
||||
import welcome from "../public/locales/en/welcome.json";
|
||||
import setting from "../public/locales/en/setting.json";
|
||||
import file from "../public/locales/en/file.json";
|
||||
import pkg from '../package.json';
|
||||
import member from "../public/locales/en/member.json";
|
||||
import setting from "../public/locales/en/setting.json";
|
||||
import welcome from "../public/locales/en/welcome.json";
|
||||
import widget from "../public/locales/en/widget.json";
|
||||
|
||||
// don't want to use this?
|
||||
// have a look at the Quick start guide
|
||||
// for passing in lng and translations on init
|
||||
@@ -64,7 +66,7 @@ i18n
|
||||
returnNull: false,
|
||||
// for backend middleware
|
||||
backend: {
|
||||
queryStringParams: { v: pkg.version },
|
||||
queryStringParams: { v: pkg.version }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Vendored
+9
-8
@@ -1,9 +1,10 @@
|
||||
import { resources, defaultNS } from "./i18n";
|
||||
import { defaultNS, resources } from "./i18n";
|
||||
|
||||
declare module "i18next" {
|
||||
interface CustomTypeOptions {
|
||||
allowObjectInHTMLChildren: true,
|
||||
returnNull: false;
|
||||
defaultNS: typeof defaultNS;
|
||||
resources: typeof resources["en"];
|
||||
}
|
||||
}
|
||||
interface CustomTypeOptions {
|
||||
allowObjectInHTMLChildren: true;
|
||||
returnNull: false;
|
||||
defaultNS: typeof defaultNS;
|
||||
resources: (typeof resources)["en"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import "dayjs/locale/zh-cn";
|
||||
import "dayjs/locale/ja";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import isToday from "dayjs/plugin/isToday";
|
||||
import isYesterday from "dayjs/plugin/isYesterday";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
|
||||
// import i18n from '../i18n';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
Vendored
+1
-1
@@ -66,7 +66,7 @@ declare module "*.module.css" {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '@emoji-mart/react';
|
||||
declare module "@emoji-mart/react";
|
||||
|
||||
interface Window {
|
||||
ethereum: any;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// import { useState } from "react";
|
||||
import { t } from 'i18next';
|
||||
import { t } from "i18next";
|
||||
|
||||
// `name` for in-code usage, `label` for display
|
||||
export interface Step {
|
||||
name: string;
|
||||
@@ -36,5 +37,4 @@ const steps: Step[] = [
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export default steps;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { clientsClaim } from "workbox-core";
|
||||
import { ExpirationPlugin } from "workbox-expiration";
|
||||
import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
|
||||
import { createHandlerBoundToURL, precacheAndRoute } from "workbox-precaching";
|
||||
import { registerRoute } from "workbox-routing";
|
||||
import { StaleWhileRevalidate } from "workbox-strategies";
|
||||
|
||||
@@ -34,7 +34,11 @@ registerRoute(
|
||||
} // If this is a URL that starts with /_, skip.
|
||||
const urlPath = url.pathname;
|
||||
// 忽略api开头的path 本地语言文件 挂件地址
|
||||
if (urlPath.startsWith("/api") || urlPath.startsWith("/locales/") || urlPath.startsWith("/widget")) {
|
||||
if (
|
||||
urlPath.startsWith("/api") ||
|
||||
urlPath.startsWith("/locales/") ||
|
||||
urlPath.startsWith("/widget")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (urlPath.startsWith("/_")) {
|
||||
|
||||
@@ -17,10 +17,10 @@ interface Config {
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === "localhost" ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === "[::1]" ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === "[::1]" ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
|
||||
);
|
||||
|
||||
export function register(config: Config) {
|
||||
@@ -46,7 +46,7 @@ export function register(config: Config) {
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
"This web app is being served cache-first by a service " +
|
||||
"worker. To learn more, visit https://cra.link/PWA"
|
||||
"worker. To learn more, visit https://cra.link/PWA"
|
||||
);
|
||||
});
|
||||
} else {
|
||||
@@ -75,7 +75,7 @@ function registerValidSW(swUrl: string, config: Config) {
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
"New content is available and will be used when all " +
|
||||
"tabs for this page are closed. See https://cra.link/PWA."
|
||||
"tabs for this page are closed. See https://cra.link/PWA."
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { User } from "./user";
|
||||
|
||||
export interface AuthToken {
|
||||
// common
|
||||
server_id: string;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//call `group` in backend
|
||||
import { ContentType } from "./message";
|
||||
export interface ChannelMember { }
|
||||
|
||||
export interface Message { }
|
||||
export interface ChannelMember {}
|
||||
|
||||
export interface Message {}
|
||||
|
||||
export interface PinnedMessage {
|
||||
mid: number;
|
||||
@@ -13,7 +14,7 @@ export interface PinnedMessage {
|
||||
properties: {
|
||||
local_id?: number;
|
||||
content_type?: string;
|
||||
size?: number
|
||||
size?: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -7,11 +7,11 @@ export type AuthType = "register" | "login";
|
||||
export type PriceType = "subscription" | "payment" | "booking";
|
||||
export type PriceSubscriptionDuration = "month" | "quarter" | "year";
|
||||
export type Price = {
|
||||
title?: string,
|
||||
price?: string,
|
||||
limit?: number,
|
||||
pid?: string,
|
||||
desc?: string,
|
||||
type: PriceType,
|
||||
sub_dur?: PriceSubscriptionDuration
|
||||
}
|
||||
title?: string;
|
||||
price?: string;
|
||||
limit?: number;
|
||||
pid?: string;
|
||||
desc?: string;
|
||||
type: PriceType;
|
||||
sub_dur?: PriceSubscriptionDuration;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ChatEvent } from "./sse";
|
||||
|
||||
export type ContentType = "text/plain" | "text/markdown" | "vocechat/file" | "vocechat/audio" | "vocechat/archive";
|
||||
export type ContentType =
|
||||
| "text/plain"
|
||||
| "text/markdown"
|
||||
| "vocechat/file"
|
||||
| "vocechat/audio"
|
||||
| "vocechat/archive";
|
||||
export type ContentTypeKey = "text" | "markdown" | "file" | "audio" | "archive";
|
||||
|
||||
export interface MuteDTO {
|
||||
@@ -24,4 +29,4 @@ export interface UploadFileResponse {
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
export interface ChatMessage extends Omit<ChatEvent, "type"> { }
|
||||
export interface ChatMessage extends Omit<ChatEvent, "type"> {}
|
||||
|
||||
@@ -56,9 +56,7 @@ export interface OG {
|
||||
secure_url?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}[
|
||||
|
||||
];
|
||||
}[];
|
||||
favicon_url?: string;
|
||||
description?: string;
|
||||
locale?: string;
|
||||
|
||||
+21
-22
@@ -6,14 +6,13 @@ export interface Server extends SystemCommon {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
export type ChatLayout = "SelfRight" | "Left"
|
||||
export type ChatLayout = "SelfRight" | "Left";
|
||||
export interface SystemCommon {
|
||||
show_user_online_status?: boolean,
|
||||
webclient_auto_update?: boolean,
|
||||
contact_verification_enable?: boolean,
|
||||
chat_layout_mode?: ChatLayout,
|
||||
max_file_expiry_mode?: "Off" | "Day1" | "Day7" | "Day30" | "Day90" | "Day180"
|
||||
|
||||
show_user_online_status?: boolean;
|
||||
webclient_auto_update?: boolean;
|
||||
contact_verification_enable?: boolean;
|
||||
chat_layout_mode?: ChatLayout;
|
||||
max_file_expiry_mode?: "Off" | "Day1" | "Day7" | "Day30" | "Day90" | "Day180";
|
||||
}
|
||||
export interface GithubAuthConfig {
|
||||
client_id?: string;
|
||||
@@ -37,26 +36,26 @@ export interface AgoraConfig {
|
||||
customer_secret: string;
|
||||
}
|
||||
export interface AgoraVoicingListResponse {
|
||||
success: boolean,
|
||||
success: boolean;
|
||||
data: {
|
||||
channels: { channel_name: string, user_count: number }[],
|
||||
total_size: number
|
||||
}
|
||||
channels: { channel_name: string; user_count: number }[];
|
||||
total_size: number;
|
||||
};
|
||||
}
|
||||
export interface AgoraChannelUsersResponse {
|
||||
success: boolean,
|
||||
success: boolean;
|
||||
data: {
|
||||
channel_exist: boolean,
|
||||
mode?: number,
|
||||
total?: number,
|
||||
users?: number[]
|
||||
}
|
||||
channel_exist: boolean;
|
||||
mode?: number;
|
||||
total?: number;
|
||||
users?: number[];
|
||||
};
|
||||
}
|
||||
export interface AgoraTokenResponse {
|
||||
agora_token: string,
|
||||
uid: number,
|
||||
channel_name: string,
|
||||
expired_in: number,
|
||||
agora_token: string;
|
||||
uid: number;
|
||||
channel_name: string;
|
||||
expired_in: number;
|
||||
app_id: string;
|
||||
}
|
||||
export interface SMTPConfig {
|
||||
@@ -107,7 +106,7 @@ export interface LicenseMetadata {
|
||||
domain: string | string[];
|
||||
}
|
||||
export interface RenewLicense {
|
||||
type: PriceType,
|
||||
type: PriceType;
|
||||
priceId: string;
|
||||
metadata: LicenseMetadata;
|
||||
cancel_url: string;
|
||||
|
||||
+17
-15
@@ -1,6 +1,6 @@
|
||||
import { ContactInfo, User } from "./user";
|
||||
import { Channel } from "./channel";
|
||||
import { ContentType } from "./message";
|
||||
import { ContactInfo, User } from "./user";
|
||||
|
||||
export interface ReadyEvent {
|
||||
type: "ready";
|
||||
@@ -47,11 +47,13 @@ export interface MuteChannel {
|
||||
expired_at: number;
|
||||
}
|
||||
export type AutoDeleteMsgForUser = {
|
||||
uid: number; expires_in: number | null
|
||||
}
|
||||
uid: number;
|
||||
expires_in: number | null;
|
||||
};
|
||||
export type AutoDeleteMsgForGroup = {
|
||||
gid: number; expires_in: number | null
|
||||
}
|
||||
gid: number;
|
||||
expires_in: number | null;
|
||||
};
|
||||
export type UserSettingsEvent = {
|
||||
type: "user_settings";
|
||||
mute_users?: MuteUser[];
|
||||
@@ -60,21 +62,21 @@ export type UserSettingsEvent = {
|
||||
read_index_groups?: { gid: number; mid: number }[];
|
||||
burn_after_reading_users?: AutoDeleteMsgForUser[];
|
||||
burn_after_reading_groups?: AutoDeleteMsgForGroup[];
|
||||
}
|
||||
};
|
||||
export type AutoDeleteSettingForUsers = {
|
||||
burn_after_reading_users: AutoDeleteMsgForUser[]
|
||||
}
|
||||
burn_after_reading_users: AutoDeleteMsgForUser[];
|
||||
};
|
||||
export type AutoDeleteSettingForChannels = {
|
||||
burn_after_reading_groups: AutoDeleteMsgForGroup[]
|
||||
}
|
||||
export type AutoDeleteMessageSettingDTO = AutoDeleteSettingForUsers | AutoDeleteSettingForChannels
|
||||
burn_after_reading_groups: AutoDeleteMsgForGroup[];
|
||||
};
|
||||
export type AutoDeleteMessageSettingDTO = AutoDeleteSettingForUsers | AutoDeleteSettingForChannels;
|
||||
export type UserSettingsChangedEvent = {
|
||||
type: "user_settings_changed";
|
||||
from_device?: string;
|
||||
add_mute_users?: MuteUser[];
|
||||
remove_mute_users?: number[];
|
||||
add_mute_groups?: MuteChannel[];
|
||||
add_contacts?: { target_uid: number, info: ContactInfo }[];
|
||||
add_contacts?: { target_uid: number; info: ContactInfo }[];
|
||||
remove_contacts?: number[];
|
||||
remove_mute_groups?: number[];
|
||||
add_pin_chats?: PinChat[];
|
||||
@@ -83,7 +85,7 @@ export type UserSettingsChangedEvent = {
|
||||
read_index_groups?: { gid: number; mid: number }[];
|
||||
burn_after_reading_users?: AutoDeleteMsgForUser[];
|
||||
burn_after_reading_groups?: AutoDeleteMsgForGroup[];
|
||||
}
|
||||
};
|
||||
|
||||
export interface RelatedGroupsEvent {
|
||||
type: "related_groups";
|
||||
@@ -96,7 +98,7 @@ export type PinChatTargetChannel = {
|
||||
gid: number;
|
||||
};
|
||||
export type PinChatTarget = PinChatTargetUser | PinChatTargetChannel;
|
||||
export type PinChat = { target: PinChatTarget[], updated_at: number }
|
||||
export type PinChat = { target: PinChatTarget[]; updated_at: number };
|
||||
export interface PinnedChatsEvent {
|
||||
type: "pinned_chats";
|
||||
chats: PinChat[];
|
||||
@@ -214,7 +216,7 @@ interface GroupClearEvent {
|
||||
interface UserCallEvent {
|
||||
type: "user_calling";
|
||||
target: number;
|
||||
uid: number
|
||||
uid: number;
|
||||
}
|
||||
|
||||
export type ServerEvent =
|
||||
|
||||
+23
-16
@@ -3,8 +3,8 @@ import { AuthToken } from "./auth";
|
||||
export type Gender = 0 | 1;
|
||||
|
||||
export interface AutoDeleteMsgDTO {
|
||||
users?: { uid: number, expires_in: number }[],
|
||||
groups?: { gid: number, expires_in: number }[],
|
||||
users?: { uid: number; expires_in: number }[];
|
||||
groups?: { gid: number; expires_in: number }[];
|
||||
}
|
||||
export interface User {
|
||||
uid: number;
|
||||
@@ -25,14 +25,14 @@ export type ContactInfo = {
|
||||
status: ContactStatus;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
};
|
||||
export interface ContactResponse {
|
||||
target_uid: number;
|
||||
target_info: User;
|
||||
contact_info: ContactInfo
|
||||
contact_info: ContactInfo;
|
||||
}
|
||||
export interface Contact extends User {
|
||||
status: ContactStatus
|
||||
status: ContactStatus;
|
||||
}
|
||||
export type UserStatus = "normal" | "frozen";
|
||||
export type UserDevice = {
|
||||
@@ -41,11 +41,11 @@ export type UserDevice = {
|
||||
is_online: boolean;
|
||||
};
|
||||
export type BotAPIKey = {
|
||||
id: number,
|
||||
name: string,
|
||||
key: string,
|
||||
created_at: number,
|
||||
last_used: number
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
created_at: number;
|
||||
last_used: number;
|
||||
};
|
||||
|
||||
export interface UserForAdmin extends User {
|
||||
@@ -58,16 +58,23 @@ export interface UserForAdmin extends User {
|
||||
export interface UserForAdminDTO extends Partial<UserForAdmin> {
|
||||
id?: number;
|
||||
}
|
||||
export interface UserDTO extends Partial<Pick<User, "name" | "gender" | "language" | "email" | "webhook_url">> {
|
||||
password?: string
|
||||
export interface UserDTO
|
||||
extends Partial<Pick<User, "name" | "gender" | "language" | "email" | "webhook_url">> {
|
||||
password?: string;
|
||||
}
|
||||
export interface UserCreateDTO extends Pick<User, "name" | "gender" | "language" | "email" | "webhook_url" | "is_bot" | "is_admin"> {
|
||||
export interface UserCreateDTO
|
||||
extends Pick<
|
||||
User,
|
||||
"name" | "gender" | "language" | "email" | "webhook_url" | "is_bot" | "is_admin"
|
||||
> {
|
||||
password: string;
|
||||
}
|
||||
export interface UserRegDTO extends Partial<Pick<User, "name" | "gender" | "language" | "email">>, Partial<Pick<UserDevice, "device" | "device_token">> {
|
||||
export interface UserRegDTO
|
||||
extends Partial<Pick<User, "name" | "gender" | "language" | "email">>,
|
||||
Partial<Pick<UserDevice, "device" | "device_token">> {
|
||||
password?: string;
|
||||
magic_token?: string
|
||||
magic_token?: string;
|
||||
}
|
||||
export interface UserRegResponse extends AuthToken {
|
||||
user: User
|
||||
user: User;
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,13 +1,15 @@
|
||||
import { useEffect } from "react";
|
||||
import initCache, { useRehydrate } from "../app/cache";
|
||||
export default function useCache() {
|
||||
const { rehydrate, rehydrated } = useRehydrate();
|
||||
useEffect(() => {
|
||||
initCache();
|
||||
rehydrate();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
rehydrated
|
||||
};
|
||||
import initCache, { useRehydrate } from "../app/cache";
|
||||
|
||||
export default function useCache() {
|
||||
const { rehydrate, rehydrated } = useRehydrate();
|
||||
useEffect(() => {
|
||||
initCache();
|
||||
rehydrate();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
rehydrated
|
||||
};
|
||||
}
|
||||
|
||||
+24
-23
@@ -1,32 +1,33 @@
|
||||
import { useEffect } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { useAppSelector } from "../app/store";
|
||||
|
||||
import useStreaming from "@/hooks/useStreaming";
|
||||
import { useAppSelector } from "../app/store";
|
||||
|
||||
export default function useSSE() {
|
||||
const {
|
||||
loginUid,
|
||||
token,
|
||||
expireTime = +new Date(),
|
||||
} = useAppSelector((store) => {
|
||||
return {
|
||||
loginUid: store.authData.user?.uid,
|
||||
token: store.authData.token,
|
||||
expireTime: store.authData.expireTime
|
||||
};
|
||||
});
|
||||
const { setStreamingReady } = useStreaming();
|
||||
const {
|
||||
loginUid,
|
||||
token,
|
||||
expireTime = +new Date()
|
||||
} = useAppSelector((store) => {
|
||||
return {
|
||||
loginUid: store.authData.user?.uid,
|
||||
token: store.authData.token,
|
||||
expireTime: store.authData.expireTime
|
||||
};
|
||||
});
|
||||
const { setStreamingReady } = useStreaming();
|
||||
|
||||
const tokenAlmostExpire = dayjs().isAfter(new Date(expireTime - 20 * 1000));
|
||||
const canStreaming = !!loginUid && !!token && !tokenAlmostExpire;
|
||||
// console.log("ttt", loginUid, rehydrated, token);
|
||||
const tokenAlmostExpire = dayjs().isAfter(new Date(expireTime - 20 * 1000));
|
||||
const canStreaming = !!loginUid && !!token && !tokenAlmostExpire;
|
||||
// console.log("ttt", loginUid, rehydrated, token);
|
||||
|
||||
useEffect(() => {
|
||||
setStreamingReady(canStreaming);
|
||||
return () => {
|
||||
setStreamingReady(false);
|
||||
};
|
||||
}, [canStreaming]);
|
||||
useEffect(() => {
|
||||
setStreamingReady(canStreaming);
|
||||
return () => {
|
||||
setStreamingReady(false);
|
||||
};
|
||||
}, [canStreaming]);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user