Files
ColdBreeze-chat-web/src/app/slices/footprint.ts
T
Tristan Yang 2f9e0ccaab feat: remark
2025-07-16 17:52:57 +08:00

283 lines
8.5 KiB
TypeScript

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 { resetAuthData } from "./auth.data";
type ChannelAside = "members" | "voice" | "voice_fullscreen" | null;
type DMAside = "voice" | "voice_fullscreen" | null;
export interface State {
og: { [url: string]: OG };
usersVersion: number;
afterMid: number;
historyUsers: { [uid: number]: string | "reached" };
historyChannels: { [cid: number]: string | "reached" };
readUsers: { [uid: number]: number };
readChannels: { [cid: number]: number };
muteUsers: { [uid: number]: { expired_at?: number } | undefined };
muteChannels: { [cid: number]: { expired_at?: number } | undefined };
autoDeleteMsgUsers: AutoDeleteMsgForUser[];
autoDeleteMsgChannels: AutoDeleteMsgForGroup[];
channelAsides: { [cid: number]: ChannelAside };
dmAsides: { [uid: number]: DMAside };
pinChats: PinChat[];
remarkMap: Record<number, string>;
}
const initialState: State = {
og: {},
usersVersion: 0,
afterMid: 0,
historyUsers: {},
historyChannels: {},
readUsers: {},
readChannels: {},
muteUsers: {},
muteChannels: {},
autoDeleteMsgUsers: [],
autoDeleteMsgChannels: [],
channelAsides: {},
dmAsides: {},
pinChats: [],
remarkMap: {},
};
const footprintSlice = createSlice({
name: "footprint",
initialState,
reducers: {
resetFootprint() {
return initialState;
},
fillFootprint(state, action: PayloadAction<State>) {
const {
og = {},
usersVersion = state.usersVersion,
afterMid = state.afterMid,
historyUsers = {},
historyChannels = {},
readUsers = {},
readChannels = {},
muteUsers = {},
muteChannels = {},
autoDeleteMsgUsers = [],
autoDeleteMsgChannels = [],
channelAsides = {},
dmAsides = {},
pinChats = [],
remarkMap = {},
} = action.payload;
// 初始化全局变量
window.USERS_VERSION = usersVersion;
window.AFTER_MID = afterMid;
return {
og,
usersVersion,
afterMid,
historyUsers,
historyChannels,
readUsers,
readChannels,
muteUsers,
muteChannels,
autoDeleteMsgUsers,
autoDeleteMsgChannels,
channelAsides,
dmAsides,
pinChats,
remarkMap,
};
},
updateUsersVersion(state, action: PayloadAction<number>) {
state.usersVersion = action.payload;
window.USERS_VERSION = action.payload;
},
updateAfterMid(state, action: PayloadAction<number>) {
const newMid = action.payload;
// 如果新 mid 小于已有的 afterMid,则不必更新
if (state.afterMid < newMid) {
state.afterMid = action.payload;
window.AFTER_MID = action.payload;
}
},
updateAutoDeleteSetting(state, action: PayloadAction<AutoDeleteMessageSettingDTO>) {
const payload = action.payload;
Object.keys(payload).forEach((key) => {
switch (key) {
case "burn_after_reading_users": {
const updates = (payload as AutoDeleteSettingForUsers).burn_after_reading_users;
updates.map((item) => {
const { uid } = item;
const idx = state.autoDeleteMsgUsers.findIndex((tmp) => tmp.uid == uid);
if (idx !== -1) {
// update
state.autoDeleteMsgUsers[idx] = item;
} else {
// add
state.autoDeleteMsgUsers.push(item);
}
});
break;
}
case "burn_after_reading_groups": {
const updates = (payload as AutoDeleteSettingForChannels).burn_after_reading_groups;
updates.map((item) => {
const { gid } = item;
const idx = state.autoDeleteMsgChannels.findIndex((tmp) => tmp.gid == gid);
if (idx !== -1) {
// update
state.autoDeleteMsgChannels[idx] = item;
} else {
// add
state.autoDeleteMsgChannels.push(item);
}
});
break;
}
default:
break;
}
});
},
updateMute(state, action: PayloadAction<MuteDTO>) {
const payload = action.payload || {};
Object.keys(payload).forEach((key) => {
switch (key) {
case "remove_users": {
const uids = payload.remove_users;
uids?.forEach((id) => {
delete state.muteUsers[id];
});
break;
}
case "remove_groups": {
const gids = payload.remove_groups;
gids?.forEach((id) => {
delete state.muteChannels[id];
});
break;
}
case "add_users": {
const mutes = payload.add_users;
mutes?.forEach(({ uid, expired_at }) => {
state.muteUsers[uid] = { expired_at };
});
break;
}
case "add_groups": {
const mutes = payload.add_groups;
mutes?.forEach(({ gid, expired_at }) => {
state.muteChannels[gid] = { expired_at };
});
break;
}
default:
break;
}
});
},
upsertPinChats(state, action: PayloadAction<{ pins: PinChat[]; override?: boolean }>) {
const { pins, override = false } = action.payload;
if (override) {
state.pinChats = pins;
} else {
state.pinChats = [...pins, ...state.pinChats];
}
},
removePinChats(state, action: PayloadAction<PinChatTarget[]>) {
const pins = action.payload;
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]);
});
},
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 }>
) {
const { type, id, mid } = action.payload;
if (type == "channel") {
state.historyChannels[id] = mid;
} else {
state.historyUsers[id] = mid;
}
},
updateReadUsers(state, action: PayloadAction<{ uid: number; mid: number }[] | undefined>) {
const reads = action.payload || [];
if (reads.length == 0) return;
reads.forEach(({ uid, mid }) => {
state.readUsers[uid] = mid;
});
},
updateRemarkByUid(state, action: PayloadAction<{ uid: number; remark: string }>) {
const { uid, remark } = action.payload;
state.remarkMap[uid] = remark;
},
updateReadChannels(state, action: PayloadAction<{ gid: number; mid: number }[] | undefined>) {
const reads = action.payload || [];
if (reads.length == 0) return;
reads.forEach(({ gid, mid }) => {
state.readChannels[gid] = mid;
});
},
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 }>) {
const { id, aside } = action.payload;
state.dmAsides[id] = aside;
},
},
extraReducers: (builder) => {
builder.addCase(resetAuthData, (state) => {
// 如果有 aside 是 voice 的,就把它关掉
Object.keys(state.channelAsides).forEach((channel_id: string) => {
if (state.channelAsides[+channel_id] === "voice") {
state.channelAsides[+channel_id] = null;
}
});
Object.keys(state.dmAsides).forEach((uid: string) => {
if (state.dmAsides[+uid] === "voice") {
state.dmAsides[+uid] = null;
}
});
});
},
});
export const {
resetFootprint,
fillFootprint,
updateAfterMid,
updateUsersVersion,
updateReadChannels,
updateReadUsers,
updateMute,
updateAutoDeleteSetting,
updateHistoryMark,
upsertOG,
updateChannelVisibleAside,
updateDMVisibleAside,
upsertPinChats,
removePinChats,
updateRemarkByUid,
} = footprintSlice.actions;
export default footprintSlice.reducer;