feat: DM infrastructure overhaul + volume sliders + LiveKit dynamic URL

- Add DM edit/delete endpoints (REST + WebSocket)
- Add DM typing indicators with real-time broadcast
- Fix volume sliders to control LiveKit mic/speaker
- Fix Send Message button on user profile popout
- Add New DM modal with user search
- Fix message pagination (SQL cursor instead of in-memory)
- Guard optional attachments on DM messages
- Dynamic LiveKit URL from request Host header
- DM sidebar auto-sorts by most recent message
This commit is contained in:
Jannis Braun
2026-02-18 17:29:39 +01:00
parent 4f65ea9c86
commit 7168149d98
31 changed files with 1189 additions and 136 deletions
+29
View File
@@ -11,6 +11,8 @@ interface VoiceState {
participants: ParticipantInfo[];
connectionError: string | null;
isLiveKitConnected: boolean;
inputVolume: number; // 0-200 (100 = default)
outputVolume: number; // 0-200 (100 = default)
setVoiceUsers: (channelId: string, userIds: string[]) => void;
addVoiceUser: (channelId: string, userId: string) => void;
removeVoiceUser: (channelId: string, userId: string) => void;
@@ -18,11 +20,15 @@ interface VoiceState {
setParticipants: (participants: ParticipantInfo[]) => void;
setConnectionError: (error: string | null) => void;
setIsLiveKitConnected: (connected: boolean) => void;
setInputVolume: (volume: number) => void;
setOutputVolume: (volume: number) => void;
toggleMic: () => void;
toggleCamera: () => void;
toggleScreenShare: () => void;
toggleDeafen: () => void;
getVoiceUsers: (channelId: string) => string[];
clearAllVoiceUsers: () => void;
leaveVoice: () => void;
reset: () => void;
}
@@ -36,6 +42,8 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
participants: [],
connectionError: null,
isLiveKitConnected: false,
inputVolume: 100,
outputVolume: 100,
setVoiceUsers: (channelId, userIds) => {
set((state) => {
@@ -71,6 +79,9 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
setConnectionError: (error) => set({ connectionError: error }),
setIsLiveKitConnected: (connected) => set({ isLiveKitConnected: connected }),
setInputVolume: (volume) => set({ inputVolume: volume }),
setOutputVolume: (volume) => set({ outputVolume: volume }),
toggleMic: () => set((state) => ({ isMuted: !state.isMuted })),
toggleDeafen: () => set((state) => ({ isDeafened: !state.isDeafened })),
toggleCamera: () => set((state) => ({ isCameraOn: !state.isCameraOn })),
@@ -78,6 +89,22 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
getVoiceUsers: (channelId) => get().voiceUsers.get(channelId) ?? [],
clearAllVoiceUsers: () => set({ voiceUsers: new Map() }),
// Leave voice without wiping the voiceUsers map (so sidebar still shows others)
leaveVoice: () => set({
currentVoiceChannelId: null,
isMuted: false,
isDeafened: false,
isCameraOn: false,
isScreenSharing: false,
participants: [],
connectionError: null,
isLiveKitConnected: false,
inputVolume: 100,
outputVolume: 100,
}),
reset: () => set({
voiceUsers: new Map(),
currentVoiceChannelId: null,
@@ -88,5 +115,7 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
participants: [],
connectionError: null,
isLiveKitConnected: false,
inputVolume: 100,
outputVolume: 100,
}),
}));