feat: Discord-style presence status selector
Adds a manual presence picker to the sidebar user avatar. Clicking the avatar opens a popout menu with Online, Idle, Do Not Disturb, Invisible, and Auto (activity-based) options. The selected status is shown as a colored badge on the avatar and stored in settings (survives reloads). usePresenceUpdater now short-circuits for manual states and only runs the full activity-tracking logic in Auto mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,13 @@ import { useMatrixClient } from './useMatrixClient';
|
||||
import { useSetting } from '../state/hooks/settings';
|
||||
import { settingsAtom } from '../state/settings';
|
||||
|
||||
const IDLE_TIMEOUT_MS = 10 * 60 * 1000; // go unavailable after 10 min of no input
|
||||
const ACTIVITY_THROTTLE_MS = 1000; // process activity events at most once per second
|
||||
const IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
const ACTIVITY_THROTTLE_MS = 1000;
|
||||
|
||||
export function usePresenceUpdater() {
|
||||
const mx = useMatrixClient();
|
||||
const [hidePresence] = useSetting(settingsAtom, 'hidePresence');
|
||||
const [presenceStatus] = useSetting(settingsAtom, 'presenceStatus');
|
||||
|
||||
const idleTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const isIdleRef = useRef(false);
|
||||
@@ -16,14 +17,29 @@ export function usePresenceUpdater() {
|
||||
|
||||
useEffect(() => {
|
||||
const setOnline = () => mx.setPresence({ presence: 'online' }).catch(() => undefined);
|
||||
const setUnavailable = () => mx.setPresence({ presence: 'unavailable' }).catch(() => undefined);
|
||||
const setUnavailable = (statusMsg?: string) =>
|
||||
mx.setPresence({ presence: 'unavailable', status_msg: statusMsg }).catch(() => undefined);
|
||||
const setOffline = () => mx.setPresence({ presence: 'offline' }).catch(() => undefined);
|
||||
|
||||
// When the user hides presence, broadcast offline and do nothing else.
|
||||
if (hidePresence) {
|
||||
mx.setPresence({ presence: 'offline' }).catch(() => undefined);
|
||||
// Manual presence overrides — no activity tracking needed.
|
||||
if (hidePresence || presenceStatus === 'invisible') {
|
||||
setOffline();
|
||||
return undefined;
|
||||
}
|
||||
if (presenceStatus === 'online') {
|
||||
setOnline();
|
||||
return undefined;
|
||||
}
|
||||
if (presenceStatus === 'idle') {
|
||||
setUnavailable();
|
||||
return undefined;
|
||||
}
|
||||
if (presenceStatus === 'dnd') {
|
||||
setUnavailable('dnd');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// presenceStatus === 'auto' — original activity-tracking behavior.
|
||||
const startIdleTimer = () => {
|
||||
clearTimeout(idleTimerRef.current);
|
||||
idleTimerRef.current = window.setTimeout(() => {
|
||||
@@ -37,7 +53,6 @@ export function usePresenceUpdater() {
|
||||
if (now - lastActivityRef.current < ACTIVITY_THROTTLE_MS) return;
|
||||
lastActivityRef.current = now;
|
||||
|
||||
// Coming back from idle while the tab is visible — go back online.
|
||||
if (isIdleRef.current && !document.hidden) {
|
||||
isIdleRef.current = false;
|
||||
setOnline();
|
||||
@@ -57,12 +72,9 @@ export function usePresenceUpdater() {
|
||||
}
|
||||
};
|
||||
|
||||
// Best-effort offline on page close. fetch+keepalive survives the page unload
|
||||
// unlike a regular async call, and avoids the bfcache penalty of beforeunload.
|
||||
const handlePageHide = () => {
|
||||
const userId = mx.getUserId();
|
||||
const token = mx.getAccessToken();
|
||||
// MatrixClient exposes baseUrl as a public property
|
||||
const baseUrl = (mx as unknown as { baseUrl: string }).baseUrl;
|
||||
if (!userId || !token || !baseUrl) return;
|
||||
|
||||
@@ -77,7 +89,6 @@ export function usePresenceUpdater() {
|
||||
}).catch(() => undefined);
|
||||
};
|
||||
|
||||
// Announce online immediately when the client mounts.
|
||||
setOnline();
|
||||
startIdleTimer();
|
||||
|
||||
@@ -92,5 +103,5 @@ export function usePresenceUpdater() {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.removeEventListener('pagehide', handlePageHide);
|
||||
};
|
||||
}, [mx, hidePresence]);
|
||||
}, [mx, hidePresence, presenceStatus]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user