Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
export interface ComradePresence {
|
|
user: string;
|
|
page: string;
|
|
online: boolean;
|
|
ts: number;
|
|
}
|
|
|
|
export interface NotesTyping {
|
|
user: string;
|
|
active: boolean;
|
|
ts: number;
|
|
}
|
|
|
|
export interface PresenceState {
|
|
comrades: Record<string, ComradePresence>;
|
|
notesTyping: NotesTyping | null;
|
|
selfUser: string | null;
|
|
}
|
|
|
|
export const initialPresenceState: PresenceState = {
|
|
comrades: {},
|
|
notesTyping: null,
|
|
selfUser: null,
|
|
};
|
|
|
|
export type PresenceAction =
|
|
| { type: 'set_self'; user: string | null }
|
|
| { type: 'presence_snapshot'; comrades: ComradePresence[] }
|
|
| { type: 'presence_update'; user: string; page: string; online: boolean; ts: number }
|
|
| { type: 'notes_typing'; user: string; active: boolean; ts: number }
|
|
| { type: 'clear_notes_typing' };
|
|
|
|
export function reducePresence(state: PresenceState, action: PresenceAction): PresenceState {
|
|
switch (action.type) {
|
|
case 'set_self':
|
|
return { ...state, selfUser: action.user };
|
|
case 'presence_snapshot': {
|
|
const comrades: Record<string, ComradePresence> = {};
|
|
for (const c of action.comrades) {
|
|
if (!c.user || !c.online) continue;
|
|
if (state.selfUser && c.user === state.selfUser) continue;
|
|
comrades[c.user] = { ...c, online: true };
|
|
}
|
|
return { ...state, comrades };
|
|
}
|
|
case 'presence_update': {
|
|
const next = { ...state.comrades };
|
|
if (!action.online || (state.selfUser && action.user === state.selfUser)) {
|
|
delete next[action.user];
|
|
return { ...state, comrades: next };
|
|
}
|
|
next[action.user] = {
|
|
user: action.user,
|
|
page: action.page || '/dashboard',
|
|
online: true,
|
|
ts: action.ts,
|
|
};
|
|
return { ...state, comrades: next };
|
|
}
|
|
case 'notes_typing': {
|
|
if (state.selfUser && action.user === state.selfUser) {
|
|
return state;
|
|
}
|
|
if (!action.active) {
|
|
if (state.notesTyping?.user === action.user) {
|
|
return { ...state, notesTyping: null };
|
|
}
|
|
return state;
|
|
}
|
|
return {
|
|
...state,
|
|
notesTyping: { user: action.user, active: true, ts: action.ts },
|
|
};
|
|
}
|
|
case 'clear_notes_typing':
|
|
return { ...state, notesTyping: null };
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
export function onlineComrades(state: PresenceState): ComradePresence[] {
|
|
return Object.values(state.comrades).filter((c) => c.online);
|
|
}
|
|
|
|
export function comradesOnPage(state: PresenceState, page: string): ComradePresence[] {
|
|
const normalized = page.startsWith('/') ? page : `/${page}`;
|
|
return onlineComrades(state).filter((c) => {
|
|
const p = c.page.startsWith('/') ? c.page : `/${c.page}`;
|
|
return p === normalized;
|
|
});
|
|
}
|