feat: Implement core Discord clone functionality including Convex backend, Electron frontend, and UI components for chat, voice, and settings.
This commit is contained in:
123
convex/voiceState.ts
Normal file
123
convex/voiceState.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { query, mutation } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
// Join voice channel
|
||||
export const join = mutation({
|
||||
args: {
|
||||
channelId: v.id("channels"),
|
||||
userId: v.id("userProfiles"),
|
||||
username: v.string(),
|
||||
isMuted: v.boolean(),
|
||||
isDeafened: v.boolean(),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
// Remove from any other voice channel first
|
||||
const existing = await ctx.db
|
||||
.query("voiceStates")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.userId))
|
||||
.collect();
|
||||
|
||||
for (const vs of existing) {
|
||||
await ctx.db.delete(vs._id);
|
||||
}
|
||||
|
||||
// Add to new channel
|
||||
await ctx.db.insert("voiceStates", {
|
||||
channelId: args.channelId,
|
||||
userId: args.userId,
|
||||
username: args.username,
|
||||
isMuted: args.isMuted,
|
||||
isDeafened: args.isDeafened,
|
||||
isScreenSharing: false,
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Leave voice channel
|
||||
export const leave = mutation({
|
||||
args: {
|
||||
userId: v.id("userProfiles"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const existing = await ctx.db
|
||||
.query("voiceStates")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.userId))
|
||||
.collect();
|
||||
|
||||
for (const vs of existing) {
|
||||
await ctx.db.delete(vs._id);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Update mute/deafen/screenshare state
|
||||
export const updateState = mutation({
|
||||
args: {
|
||||
userId: v.id("userProfiles"),
|
||||
isMuted: v.optional(v.boolean()),
|
||||
isDeafened: v.optional(v.boolean()),
|
||||
isScreenSharing: v.optional(v.boolean()),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const existing = await ctx.db
|
||||
.query("voiceStates")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.userId))
|
||||
.collect();
|
||||
|
||||
if (existing.length > 0) {
|
||||
const updates: Record<string, boolean> = {};
|
||||
if (args.isMuted !== undefined) updates.isMuted = args.isMuted;
|
||||
if (args.isDeafened !== undefined) updates.isDeafened = args.isDeafened;
|
||||
if (args.isScreenSharing !== undefined)
|
||||
updates.isScreenSharing = args.isScreenSharing;
|
||||
|
||||
await ctx.db.patch(existing[0]._id, updates);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Get all voice states (reactive!)
|
||||
export const getAll = query({
|
||||
args: {},
|
||||
returns: v.any(),
|
||||
handler: async (ctx) => {
|
||||
const states = await ctx.db.query("voiceStates").collect();
|
||||
|
||||
// Group by channel
|
||||
const grouped: Record<
|
||||
string,
|
||||
Array<{
|
||||
userId: string;
|
||||
username: string;
|
||||
isMuted: boolean;
|
||||
isDeafened: boolean;
|
||||
isScreenSharing: boolean;
|
||||
}>
|
||||
> = {};
|
||||
|
||||
for (const s of states) {
|
||||
const channelId = s.channelId;
|
||||
if (!grouped[channelId]) {
|
||||
grouped[channelId] = [];
|
||||
}
|
||||
grouped[channelId].push({
|
||||
userId: s.userId,
|
||||
username: s.username,
|
||||
isMuted: s.isMuted,
|
||||
isDeafened: s.isDeafened,
|
||||
isScreenSharing: s.isScreenSharing,
|
||||
});
|
||||
}
|
||||
|
||||
return grouped;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user