refactor: add typescript support to project

This commit is contained in:
HD
2022-06-20 17:44:42 +08:00
parent 45419bbcf4
commit c2c051d94c
25 changed files with 407 additions and 222 deletions
+5 -8
View File
@@ -3,6 +3,7 @@ import { nanoid } from "@reduxjs/toolkit";
import baseQuery from "./base.query";
import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
import BASE_URL, { KEY_DEVICE_KEY } from "../config";
import { AuthData } from '../../types/auth';
const getDeviceId = () => {
let d = localStorage.getItem(KEY_DEVICE_KEY);
@@ -27,7 +28,7 @@ export const authApi = createApi({
device_token: "test"
}
}),
transformResponse: (data) => {
transformResponse: (data: AuthData) => {
const { avatar_updated_at } = data.user;
data.user.avatar =
avatar_updated_at == 0
@@ -116,12 +117,10 @@ export const authApi = createApi({
})
}),
getCredentials: builder.query({
query: () => ({
url: `/token/credentials`
})
query: () => ({ url: "/token/credentials" })
}),
logout: builder.query({
query: () => ({ url: `token/logout` }),
query: () => ({ url: "token/logout" }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
@@ -132,9 +131,7 @@ export const authApi = createApi({
}
}),
getInitialized: builder.query({
query: () => ({
url: `/admin/system/initialized`
}),
query: () => ({ url: "/admin/system/initialized" }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data: isInitialized } = await queryFulfilled;
@@ -3,6 +3,7 @@ import toast from "react-hot-toast";
import dayjs from "dayjs";
import { updateToken, resetAuthData } from "../slices/auth.data";
import BASE_URL, { tokenHeader } from "../config";
const whiteList = [
"login",
"register",
@@ -20,6 +21,7 @@ const whiteList = [
"getInitialized",
"createAdmin"
];
const baseQuery = fetchBaseQuery({
baseUrl: BASE_URL,
prepareHeaders: (headers, { getState, endpoint }) => {
@@ -31,6 +33,7 @@ const baseQuery = fetchBaseQuery({
return headers;
}
});
let waitingForRenew = null;
const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
if (waitingForRenew) {
@@ -9,6 +9,7 @@ import { removeChannelSession } from "../slices/message.channel";
import { removeReactionMessage } from "../slices/message.reaction";
import { onMessageSendStarted } from "./handlers";
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
export const channelApi = createApi({
reducerPath: "channelApi",
baseQuery,
+1 -24
View File
@@ -1,5 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { KEY_EXPIRE, KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID } from '../config';
import { AuthData, AuthToken } from '../../types/auth';
interface State {
initialized: boolean;
@@ -25,30 +26,6 @@ const emptyState: State = {
refreshToken: null
};
interface AuthToken {
// common
server_id: string;
token: string;
refresh_token: string;
expired_in: number;
}
interface User {
uid: number;
email: string;
name: string;
gender: number;
language: string;
is_admin: boolean;
avatar_updated_at: number;
create_by: string;
}
interface AuthData extends AuthToken {
initialized?: boolean;
user: User;
}
const authDataSlice = createSlice({
name: 'authData',
initialState,
-86
View File
@@ -1,86 +0,0 @@
import { createSlice } from "@reduxjs/toolkit";
import { getNonNullValues } from "../../common/utils";
import BASE_URL from "../config";
const initialState = {
ids: [],
byId: {}
};
const contactsSlice = createSlice({
name: `contacts`,
initialState,
reducers: {
resetContacts() {
return initialState;
},
fullfillContacts(state, action) {
console.log("set Contacts store", action);
const contacts = action.payload || [];
state.ids = contacts.map(({ uid }) => uid);
state.byId = Object.fromEntries(
contacts.map((c) => {
const { uid } = c;
return [uid, c];
})
);
},
removeContact(state, action) {
const uid = action.payload;
state.ids = state.ids.filter((i) => i != uid);
delete state.byId[uid];
},
updateUsersByLogs(state, action) {
const changeLogs = action.payload;
changeLogs.forEach(({ action, uid, ...rest }) => {
switch (action) {
case "update":
{
const vals = getNonNullValues(rest);
if (state.byId[uid]) {
Object.keys(vals).forEach((k) => {
state.byId[uid][k] = vals[k];
if (k == "avatar_updated_at") {
state.byId[uid].avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`;
}
});
}
}
break;
case "create":
{
state.byId[uid] = { uid, ...rest };
const idx = state.ids.findIndex((i) => i == uid);
if (idx == -1) {
state.ids.push(uid);
}
}
break;
case "delete":
{
const idx = state.ids.findIndex((i) => i == uid);
if (idx > -1) {
state.ids.splice(idx, 1);
delete state.byId[uid];
}
}
break;
default:
break;
}
});
},
updateUsersStatus(state, action) {
const onlines = action.payload;
onlines.forEach((data) => {
const { uid, online = false } = data;
// console.log("update user status", curr, online);
if (state.byId[uid]) {
state.byId[uid].online = online;
}
});
}
}
});
export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } =
contactsSlice.actions;
export default contactsSlice.reducer;
+106
View File
@@ -0,0 +1,106 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { getNonNullValues } from '../../common/utils';
import BASE_URL from '../config';
import { User } from '../../types/auth';
import { UserLog, UserState } from '../../types/sse';
export interface StoredUser extends User {
online?: boolean;
avatar: string;
}
export interface State {
ids: number[];
byId: { [id: number]: StoredUser | undefined };
}
const initialState: State = {
ids: [],
byId: {}
};
const contactsSlice = createSlice({
name: 'contacts',
initialState,
reducers: {
resetContacts() {
return initialState;
},
fullfillContacts(state, action: PayloadAction<User[]>) {
console.log('set Contacts store', action);
const contacts = action.payload || [];
state.ids = contacts.map(({ uid }) => uid);
// old code
// state.byId = Object.fromEntries(
// contacts.map((c) => {
// const { uid } = c;
// return [uid, c];
// })
// );
contacts.forEach(u => {
state.byId[u.uid] = {
...u,
// todo: add avatar field
avatar: ''
};
});
},
removeContact(state, action: PayloadAction<number>) {
const uid = action.payload;
state.ids = state.ids.filter((i) => i != uid);
delete state.byId[uid];
},
updateUsersByLogs(state, action: PayloadAction<UserLog[]>) {
const changeLogs = action.payload;
changeLogs.forEach(({ action, uid, ...rest }) => {
switch (action) {
case 'update': {
const vals = getNonNullValues(rest);
if (state.byId[uid]) {
Object.keys(vals).forEach((k) => {
state.byId[uid][k] = vals[k];
if (k == 'avatar_updated_at') {
state.byId[uid]!.avatar = `${BASE_URL}/resource/avatar?uid=${uid}&t=${vals[k]}`;
}
});
}
break;
}
case 'create': {
// todo: missing properties avatar, create_by
state.byId[uid] = { uid, ...rest };
const idx = state.ids.findIndex((i) => i == uid);
if (idx == -1) {
state.ids.push(uid);
}
break;
}
case 'delete': {
const idx = state.ids.findIndex((i) => i == uid);
if (idx > -1) {
state.ids.splice(idx, 1);
delete state.byId[uid];
}
break;
}
default:
break;
}
});
},
updateUsersStatus(state, action: PayloadAction<UserState[]>) {
action.payload.forEach((data) => {
const { uid, online = false } = data;
// console.log("update user status", curr, online);
if (state.byId[uid]) {
state.byId[uid]!.online = online;
}
});
}
}
});
export const { resetContacts, fullfillContacts, updateUsersByLogs, updateUsersStatus } =
contactsSlice.actions;
export default contactsSlice.reducer;
@@ -1,24 +1,32 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// import BASE_URL from "../config";
const initialState = [];
// todo: check messages type
export interface Favorite {
id: number;
messages: any[];
}
const initialState: Favorite[] = [];
const favoritesSlice = createSlice({
name: `favorites`,
initialState,
reducers: {
fullfillFavorites(state, action) {
fullfillFavorites(state, action: PayloadAction<Favorite[]>) {
return action.payload;
},
addFavorite(state, action) {
addFavorite(state, action: PayloadAction<Favorite>) {
state.push(action.payload);
},
deleteFavorite(state, action) {
deleteFavorite(state, action: PayloadAction<number>) {
const id = action.payload;
const idx = state.findIndex((f) => f.id == id);
if (idx > -1) {
state.splice(idx, 1);
}
},
populateFavorite(state, action) {
populateFavorite(state, action: PayloadAction<Favorite>) {
const { id, messages } = action.payload;
const idx = state.findIndex((fav) => fav.id == id);
if (idx > -1) {
@@ -27,6 +35,8 @@ const favoritesSlice = createSlice({
}
}
});
export const { addFavorite, deleteFavorite, fullfillFavorites, populateFavorite } =
favoritesSlice.actions;
export default favoritesSlice.reducer;
@@ -1,5 +1,15 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface State {
usersVersion: number;
afterMid: number;
readUsers: {};
readChannels: {};
muteUsers: {};
muteChannels: {};
}
const initialState: State = {
usersVersion: 0,
afterMid: 0,
readUsers: {},
@@ -7,6 +17,7 @@ const initialState = {
muteUsers: {},
muteChannels: {}
};
const footprintSlice = createSlice({
name: "footprint",
initialState,
@@ -32,13 +43,11 @@ const footprintSlice = createSlice({
muteChannels
};
},
updateUsersVersion(state, action) {
const usersVersion = action.payload;
state.usersVersion = usersVersion;
updateUsersVersion(state, action: PayloadAction<number>) {
state.usersVersion = action.payload;
},
updateAfterMid(state, action) {
const afterMid = action.payload;
state.afterMid = afterMid;
updateAfterMid(state, action: PayloadAction<number>) {
state.afterMid = action.payload;
},
updateMute(state, action) {
const payload = action.payload || {};
@@ -97,6 +106,7 @@ const footprintSlice = createSlice({
}
}
});
export const {
resetFootprint,
fullfillFootprint,
@@ -106,4 +116,5 @@ export const {
updateReadUsers,
updateMute
} = footprintSlice.actions;
export default footprintSlice.reducer;
@@ -1,5 +1,12 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {};
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { ChannelMessage } from '../../types/channel';
import { EntityId } from '../../types/common';
export interface State {
[gid: number]: number[] | undefined;
}
const initialState: State = {};
const channelMsgSlice = createSlice({
name: "channelMessage",
@@ -8,41 +15,41 @@ const channelMsgSlice = createSlice({
resetChannelMsg() {
return initialState;
},
fullfillChannelMsg(state, action) {
fullfillChannelMsg(state, action: PayloadAction<State>) {
return action.payload;
},
addChannelMsg(state, action) {
const { id, mid, local_id = null } = action.payload;
if (state[id]) {
const midExsited = state[id].findIndex((id) => id == mid) > -1;
const localMsgExsited = state[id].findIndex((id) => id == local_id) > -1;
const midExsited = state[id]!.findIndex((id) => id == mid) > -1;
const localMsgExsited = state[id]!.findIndex((id) => id == local_id) > -1;
if (midExsited || localMsgExsited) return;
state[id].push(+mid);
state[id]!.push(+mid);
} else {
state[id] = [+mid];
}
},
removeChannelMsg(state, action) {
removeChannelMsg(state, action: PayloadAction<ChannelMessage & EntityId>) {
const { id, mid } = action.payload;
if (state[id]) {
const idx = state[id].findIndex((i) => i == mid);
const idx = state[id]!.findIndex((i) => i == mid);
if (idx > -1) {
// 存在 则再删除
state[id].splice(idx, 1);
state[id]!.splice(idx, 1);
}
}
},
replaceChannelMsg(state, action) {
const { id, localMid, serverMid } = action.payload;
if (state[id]) {
const localIdx = state[id].findIndex((i) => i == localMid);
const localIdx = state[id]!.findIndex((i) => i == localMid);
if (localIdx > -1 && serverMid) {
// 存在 则再删除
state[id].splice(localIdx, 1, serverMid);
state[id]!.splice(localIdx, 1, serverMid);
}
}
},
removeChannelSession(state, action) {
removeChannelSession(state, action: PayloadAction<number | number[]>) {
const ids = Array.isArray(action.payload) ? action.payload : [action.payload];
ids.forEach((id) => {
delete state[id];
@@ -50,6 +57,7 @@ const channelMsgSlice = createSlice({
}
}
});
export const {
removeChannelSession,
resetChannelMsg,
@@ -58,4 +66,5 @@ export const {
removeChannelMsg,
replaceChannelMsg
} = channelMsgSlice.actions;
export default channelMsgSlice.reducer;
@@ -1,6 +1,7 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const initialState: number[] = [];
const initialState = [];
const fileMessageSlice = createSlice({
name: "fileMessage",
initialState,
@@ -8,10 +9,10 @@ const fileMessageSlice = createSlice({
resetFileMessage() {
return initialState;
},
fullfillFileMessage(state, action) {
fullfillFileMessage(state, action: PayloadAction<number[]>) {
return action.payload || [];
},
addFileMessage(state, action) {
addFileMessage(state, action: PayloadAction<number>) {
const mid = action.payload;
// 加入file message 列表
const fidIdx = state.findIndex((fid) => fid == mid);
@@ -19,7 +20,7 @@ const fileMessageSlice = createSlice({
state.unshift(+mid);
}
},
removeFileMessage(state, action) {
removeFileMessage(state, action: PayloadAction<number | number[]>) {
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
mids.forEach((id) => {
// 从file message 列表删掉
@@ -31,6 +32,8 @@ const fileMessageSlice = createSlice({
}
}
});
export const { removeFileMessage, resetFileMessage, fullfillFileMessage, addFileMessage } =
fileMessageSlice.actions;
export default fileMessageSlice.reducer;
@@ -1,6 +1,13 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const initialState = {};
// todo: check entity type
export interface State {
[mid: number]: {
[reaction: number]: number[]
} | undefined;
}
const initialState: State = {};
const reactionMessageSlice = createSlice({
name: "reactionMessage",
initialState,
@@ -8,10 +15,10 @@ const reactionMessageSlice = createSlice({
resetReactionMessage() {
return initialState;
},
fullfillReactionMessage(state, action) {
fullfillReactionMessage(state, action: PayloadAction<State>) {
return action.payload;
},
removeReactionMessage(state, action) {
removeReactionMessage(state, action: PayloadAction<number | number[]>) {
const mids = Array.isArray(action.payload) ? action.payload : [action.payload];
mids.forEach((id) => {
delete state[id];
@@ -20,39 +27,40 @@ const reactionMessageSlice = createSlice({
toggleReactionMessage(state, action) {
// rid: reaction's mid, mid: which message append to
const { from_uid, mid, rid, action: reaction } = action.payload;
console.log("msg reaction", mid, from_uid, reaction);
const ridExisted = state[rid] || false;
// 已经塞过了
if (ridExisted) return;
console.log("ssss");
// 还未塞过任何一表情
if (!state[mid]) {
state[mid] = {};
}
// 存在该表情数据
if (state[mid][reaction]) {
const reactionUids = state[mid][reaction];
if (state[mid]![reaction]) {
const reactionUids = state[mid]![reaction];
const idx = reactionUids.findIndex((id) => id == from_uid);
if (idx > -1) {
reactionUids.splice(idx, 1);
if (reactionUids.length == 0) {
// 没有表情了
delete state[mid][reaction];
delete state[mid]![reaction];
}
} else {
reactionUids.push(from_uid);
}
} else {
state[mid][reaction] = [from_uid];
state[mid]![reaction] = [from_uid];
}
// todo: ???
state[rid] = true;
}
}
});
export const {
removeReactionMessage,
resetReactionMessage,
fullfillReactionMessage,
toggleReactionMessage
} = reactionMessageSlice.actions;
export default reactionMessageSlice.reducer;
@@ -1,9 +1,15 @@
import { createSlice } from "@reduxjs/toolkit";
import BASE_URL, { ContentTypes } from "../config";
import { isImage } from "../../common/utils";
const initialState = {
export interface State {
replying: {};
}
const initialState: State = {
replying: {}
};
const messageSlice = createSlice({
name: "message",
initialState,
@@ -66,6 +72,7 @@ const messageSlice = createSlice({
}
}
});
export const {
resetMessage,
fullfillMessage,
@@ -76,4 +83,5 @@ export const {
addReplyingMessage,
removeReplyingMessage
} = messageSlice.actions;
export default messageSlice.reducer;
@@ -1,8 +1,16 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface State {
ids: string[];
// todo: check object type
byId: { [id: number]: any; };
}
const initialState: State = {
ids: [],
byId: {}
};
const userMsgSlice = createSlice({
name: "userMessage",
initialState,
@@ -10,7 +18,7 @@ const userMsgSlice = createSlice({
resetUserMsg() {
return initialState;
},
fullfillUserMsg(state, action) {
fullfillUserMsg(state, action: PayloadAction<{ [id: string]: any; }>) {
state.ids = Object.keys(action.payload);
state.byId = action.payload;
},
+29 -4
View File
@@ -1,6 +1,28 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Views } from "../config";
const initialState = {
export interface State {
online: boolean;
ready: boolean;
userGuide: {
visible: boolean;
step: number;
};
inputMode: "text";
menuExpand: boolean;
// todo
fileListView: string;
uploadFiles: { [key: string]: any };
selectMessages: { [key: string]: any };
draftMarkdown: { [key: string]: any };
draftMixedText: { [key: string]: any };
remeberedNavs: {
chat: null | string;
contact: null | string;
};
}
const initialState: State = {
online: true,
ready: false,
userGuide: {
@@ -14,11 +36,13 @@ const initialState = {
selectMessages: {},
draftMarkdown: {},
draftMixedText: {},
// todo: typo
remeberedNavs: {
chat: null,
contact: null
}
};
const uiSlice = createSlice({
name: "ui",
initialState,
@@ -41,7 +65,7 @@ const uiSlice = createSlice({
updateFileListView(state, action) {
state.fileListView = action.payload;
},
updateRemeberedNavs(state, action) {
updateRemeberedNavs(state, action: PayloadAction<{ key?: string; path: string | null }>) {
const { key = "chat", path = null } = action.payload || {};
state.remeberedNavs[key] = path;
},
@@ -59,7 +83,6 @@ const uiSlice = createSlice({
state.userGuide[key] = obj[key];
});
},
updateUploadFiles(state, action) {
const { context = "channel", id = null, operation = "add", ...rest } = action.payload;
if (!id || !context) return;
@@ -144,6 +167,7 @@ const uiSlice = createSlice({
}
}
});
export const {
fullfillUI,
setReady,
@@ -158,4 +182,5 @@ export const {
updateDraftMixedText,
updateRemeberedNavs
} = uiSlice.actions;
export default uiSlice.reducer;