feat: add frontend Rich Presence — activityStore, ActivityCard, sidebar upgrades, settings toggle
- Task 7: Create activityStore (Zustand) with Map<userId, Activity[]>, showActivity toggle, debounced pushActivities, and reset on logout - Task 8: Wire WS integration — presence_update propagates activities to store, ready payload initializes userActivities and showActivity (via setState to avoid side effects) - Task 9: Create ActivityCard component with compact/full modes, type-colored labels, elapsed time display, asset images, and fallback to customStatus - Task 10: Upgrade ActivityPanel to three groups — active friends (full cards, no header), online friends (compact), offline friends — using getPrimaryActivity for grouping - Task 11: Upgrade MemberSidebar to show compact ActivityCards instead of raw customStatus - Task 12: Add showActivity toggle in AccountPanel settings (Activity Status section) with API persistence and store sync
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { create } from 'zustand';
|
||||
import type { Activity } from '@backspace/shared';
|
||||
import { wsSend } from '../hooks/useWebSocket';
|
||||
|
||||
let pushTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
interface ActivityState {
|
||||
userActivities: Map<string, Activity[]>;
|
||||
showActivity: boolean;
|
||||
|
||||
setUserActivities: (userId: string, activities: Activity[]) => void;
|
||||
clearUserActivities: (userId: string) => void;
|
||||
initActivities: (activityMap: Record<string, Activity[]>) => void;
|
||||
setShowActivity: (show: boolean) => void;
|
||||
pushActivities: (activities: Activity[]) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export const useActivityStore = create<ActivityState>((set, get) => ({
|
||||
userActivities: new Map(),
|
||||
showActivity: true,
|
||||
|
||||
setUserActivities: (userId, activities) => {
|
||||
set((state) => {
|
||||
const next = new Map(state.userActivities);
|
||||
if (activities.length === 0) {
|
||||
next.delete(userId);
|
||||
} else {
|
||||
next.set(userId, activities);
|
||||
}
|
||||
return { userActivities: next };
|
||||
});
|
||||
},
|
||||
|
||||
clearUserActivities: (userId) => {
|
||||
set((state) => {
|
||||
const next = new Map(state.userActivities);
|
||||
next.delete(userId);
|
||||
return { userActivities: next };
|
||||
});
|
||||
},
|
||||
|
||||
initActivities: (activityMap) => {
|
||||
const next = new Map<string, Activity[]>();
|
||||
for (const [userId, activities] of Object.entries(activityMap)) {
|
||||
if (activities.length > 0) {
|
||||
next.set(userId, activities);
|
||||
}
|
||||
}
|
||||
set({ userActivities: next });
|
||||
},
|
||||
|
||||
setShowActivity: (show) => {
|
||||
set({ showActivity: show });
|
||||
if (!show) {
|
||||
if (pushTimer) { clearTimeout(pushTimer); pushTimer = null; }
|
||||
wsSend({ type: 'activity_update', activities: [] });
|
||||
}
|
||||
},
|
||||
|
||||
pushActivities: (activities) => {
|
||||
if (!get().showActivity) return;
|
||||
if (pushTimer) clearTimeout(pushTimer);
|
||||
pushTimer = setTimeout(() => {
|
||||
wsSend({ type: 'activity_update', activities });
|
||||
pushTimer = null;
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
if (pushTimer) { clearTimeout(pushTimer); pushTimer = null; }
|
||||
set({ userActivities: new Map(), showActivity: true });
|
||||
},
|
||||
}));
|
||||
@@ -6,6 +6,7 @@ import { useSpaceStore } from './spaceStore';
|
||||
import { useSocialStore } from './socialStore';
|
||||
import { useVoiceStore } from './voiceStore';
|
||||
import { useInstanceStore } from './instanceStore';
|
||||
import { useActivityStore } from './activityStore';
|
||||
import { syncProfileUpdateToRemotes } from '../utils/profileSync';
|
||||
import { changePasswordOnRemotes, deleteAccountOnRemotes, type FederationOpResult } from '../utils/federationOps';
|
||||
import { clearSelfIds } from '../utils/identity';
|
||||
@@ -35,6 +36,7 @@ function resetUserStores() {
|
||||
useSocialStore.getState().reset();
|
||||
useVoiceStore.getState().resetSession();
|
||||
useInstanceStore.getState().reset();
|
||||
useActivityStore.getState().reset();
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
|
||||
Reference in New Issue
Block a user