multi-network: tab title sums unread across all networks (dynamic-subscription hook)
This commit is contained in:
parent
7a0624bde2
commit
688bd5daea
2 changed files with 33 additions and 3 deletions
|
|
@ -6,7 +6,7 @@ import { Chat } from './components/Chat';
|
|||
import { refreshPush } from './services/push';
|
||||
import { getConfig } from './core/config';
|
||||
import { usePluginRegistry, matchShortcut } from './modules/registry';
|
||||
import { useActiveChat, activeStore } from './core/networks';
|
||||
import { activeStore, useAllNetworksUnread } from './core/networks';
|
||||
import { useChat } from './core/store';
|
||||
|
||||
// Shown while a site handoff connects, so visitors who already chose a pseudo
|
||||
|
|
@ -28,8 +28,8 @@ export default function App() {
|
|||
const status = useChat((s) => s.status);
|
||||
const everRegistered = useChat((s) => s.everRegistered);
|
||||
const autoConnecting = useChat((s) => s.autoConnecting);
|
||||
const unread = useActiveChat((s) =>
|
||||
Object.values(s.buffers).reduce((acc, b) => acc + b.unread, 0));
|
||||
// Tab title reflects unread across ALL connected networks, not just the active one.
|
||||
const unread = useAllNetworksUnread();
|
||||
|
||||
useEffect(() => {
|
||||
// Title follows the configured brand name (config.branding.name) — the single
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
// networks and which one is ACTIVE; the chat UI reads the active network's store
|
||||
// (routing + UI land in later increments). Today it holds only the primary
|
||||
// network, so single-network behaviour is exactly preserved.
|
||||
import { useSyncExternalStore } from 'react';
|
||||
import { create, useStore } from 'zustand';
|
||||
import { createChatStore, useChat } from './store';
|
||||
import { getConfig } from './config';
|
||||
|
|
@ -93,6 +94,35 @@ export function useActiveChat<T>(selector: (s: ChatState) => T): T {
|
|||
return useStore(store, selector);
|
||||
}
|
||||
|
||||
// ── total unread across ALL networks (for the tab title) ────────────────────
|
||||
// A dynamic-subscription hook: subscribe to every network's store AND to the
|
||||
// registry, re-wiring the per-store subscriptions only when the networks list
|
||||
// actually changes (add/remove) — NOT on a plain switch (setActive keeps the same
|
||||
// array ref). The snapshot is a number, so referential equality stops any loop.
|
||||
const unreadOf = (store: ChatStore) =>
|
||||
Object.values(store.getState().buffers).reduce((a, b) => a + (b.unread || 0), 0);
|
||||
|
||||
function totalUnread(): number {
|
||||
return useNetworks.getState().networks.reduce((sum, n) => sum + unreadOf(n.store), 0);
|
||||
}
|
||||
function subscribeUnread(cb: () => void): () => void {
|
||||
let list = useNetworks.getState().networks;
|
||||
let unsubs = list.map((n) => n.store.subscribe(cb));
|
||||
const off = useNetworks.subscribe((s) => {
|
||||
if (s.networks !== list) { // networks added/removed → re-wire
|
||||
list = s.networks;
|
||||
unsubs.forEach((u) => u());
|
||||
unsubs = list.map((n) => n.store.subscribe(cb));
|
||||
}
|
||||
cb();
|
||||
});
|
||||
return () => { off(); unsubs.forEach((u) => u()); };
|
||||
}
|
||||
/** Sum of unread across every connected network (single-network: = the primary). */
|
||||
export function useAllNetworksUnread(): number {
|
||||
return useSyncExternalStore(subscribeUnread, totalUnread);
|
||||
}
|
||||
|
||||
/** Re-add + reconnect the extra networks saved from a previous session. Called
|
||||
* once at boot; each entry is isolated so one bad/failed network can't block the
|
||||
* rest or the app. Passwords come from sessionStorage (may be absent → guest). */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue