feat: show unseen count in macos dock

Resolves #93
This commit is contained in:
talksik
2026-03-30 13:53:50 -07:00
parent 65ea901626
commit a5e1ea1acf
5 changed files with 46 additions and 0 deletions
+3
View File
@@ -12,6 +12,9 @@ declare global {
fetchMetadata: (url: string) => Promise<LinkMetadata | null>;
openExternal: (url: string) => Promise<void>;
};
electronApp: {
setDockBadge: (count: number) => void;
};
}
}
+4
View File
@@ -11,6 +11,8 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useViewModeStore } from "@/stores/view-mode-store";
import { useStreamParticles } from "@/hooks/use-stream-particles";
import { useStreamKeyboardNav } from "@/hooks/use-stream-keyboard-nav";
import { useDockBadge } from "@/hooks/use-dock-badge";
import { useAuthStore } from "@/stores/auth-store";
/**
* Route-level component for /:networkId (index).
@@ -24,6 +26,8 @@ export default function NetworkRoot() {
const setViewMode = useViewModeStore((s) => s.setViewMode);
const { streams } = useStreamParticles(path);
const userId = useAuthStore((s) => s.user?.id);
useDockBadge(streams, userId);
const [composeActive, setComposeActive] = useState(false);
const { selectedIndex } = useStreamKeyboardNav({
+27
View File
@@ -0,0 +1,27 @@
import { useEffect, useMemo } from "react";
import type { Particle, StreamProperties } from "@/api/types";
type StreamParticle = Particle & { type: "stream"; properties: StreamProperties };
/**
* Syncs the macOS dock badge with the count of unseen streams.
* Both last_child_created_at and playback_markers store the child particle's
* created_at, so they're directly comparable.
*/
export function useDockBadge(streams: StreamParticle[], userId: string | undefined) {
const unseenCount = useMemo(() => {
if (!userId) return 0;
return streams.filter((s) => {
const lastActivity = s.last_child_created_at?.getTime();
if (!lastActivity) return false;
const marker = s.playback_markers?.[userId]?.getTime();
if (marker === undefined) return false;
return lastActivity > marker;
}).length;
}, [streams, userId]);
useEffect(() => {
window.electronApp.setDockBadge(unseenCount);
return () => window.electronApp.setDockBadge(0);
}, [unseenCount]);
}
+8
View File
@@ -163,6 +163,14 @@ async function fetchLinkMetadata(url: string): Promise<LinkMetadata | null> {
}
}
// --- Dock badge ---
ipcMain.on('app:set-dock-badge', (_event, count: number) => {
if (process.platform === 'darwin') {
app.dock?.setBadge(count > 0 ? String(count) : '');
}
});
ipcMain.handle('link:fetch-metadata', async (_event, url: string) => {
if (typeof url !== 'string') return null;
try {
+4
View File
@@ -13,3 +13,7 @@ contextBridge.exposeInMainWorld('electronLink', {
fetchMetadata: (url: string) => ipcRenderer.invoke('link:fetch-metadata', url),
openExternal: (url: string) => ipcRenderer.invoke('link:open-external', url),
});
contextBridge.exposeInMainWorld('electronApp', {
setDockBadge: (count: number) => ipcRenderer.send('app:set-dock-badge', count),
});