fix: dock app badge shows incorrect number
Closes #122 We now only show number based on open streams
This commit is contained in:
@@ -18,6 +18,7 @@ import { particlePath } from "@/lib/particle-path";
|
|||||||
import { useParticle } from "@/hooks/use-particle";
|
import { useParticle } from "@/hooks/use-particle";
|
||||||
import type { Particle } from "@/api/types";
|
import type { Particle } from "@/api/types";
|
||||||
import { PropsWithChildren, useCallback } from "react";
|
import { PropsWithChildren, useCallback } from "react";
|
||||||
|
import { useDockBadge } from "@/hooks/use-dock-badge";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
function getParticleDisplayName(particle: Particle): string {
|
function getParticleDisplayName(particle: Particle): string {
|
||||||
@@ -170,6 +171,9 @@ function TopBar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Layout({ children }: PropsWithChildren) {
|
export default function Layout({ children }: PropsWithChildren) {
|
||||||
|
const { networkId } = useParams();
|
||||||
|
useDockBadge(networkId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col">
|
<div className="flex h-screen flex-col">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { ComposeOverlay } from "./compose/compose-overlay";
|
|||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { useStreamParticles } from "@/hooks/use-stream-particles";
|
import { useStreamParticles } from "@/hooks/use-stream-particles";
|
||||||
import { useStreamKeyboardNav } from "@/hooks/use-stream-keyboard-nav";
|
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).
|
* Route-level component for /:networkId (index).
|
||||||
@@ -21,8 +19,6 @@ export default function NetworkRoot() {
|
|||||||
const path = particlePath(networkId!, []);
|
const path = particlePath(networkId!, []);
|
||||||
|
|
||||||
const { streams, isLoading } = useStreamParticles(path);
|
const { streams, isLoading } = useStreamParticles(path);
|
||||||
const userId = useAuthStore((s) => s.user?.id);
|
|
||||||
useDockBadge(streams, userId);
|
|
||||||
const [composeActive, setComposeActive] = useState(false);
|
const [composeActive, setComposeActive] = useState(false);
|
||||||
const [statusTab, setStatusTab] = useState<"open" | "closed">("open");
|
const [statusTab, setStatusTab] = useState<"open" | "closed">("open");
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,52 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { where } from "firebase/firestore";
|
||||||
|
import { useLiveParticleChildren } from "@/hooks/use-particle";
|
||||||
|
import { useAuthStore } from "@/stores/auth-store";
|
||||||
|
import { particlePath } from "@/lib/particle-path";
|
||||||
import type { Particle, StreamProperties } from "@/api/types";
|
import type { Particle, StreamProperties } from "@/api/types";
|
||||||
|
|
||||||
type StreamParticle = Particle & { type: "stream"; properties: StreamProperties };
|
type StreamParticle = Particle & { type: "stream"; properties: StreamProperties };
|
||||||
|
|
||||||
|
const openStatusFilter = where("status", "==", "open");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs the macOS dock badge with the count of unseen streams.
|
* Self-contained hook that syncs the macOS dock badge with the count of
|
||||||
* Both last_child_created_at and playback_markers store the child particle's
|
* unseen open streams the current user is involved in.
|
||||||
* created_at, so they're directly comparable.
|
*
|
||||||
|
* Sets up its own Firestore listener so it works independently of
|
||||||
|
* whatever stream list is rendered on screen.
|
||||||
*/
|
*/
|
||||||
export function useDockBadge(streams: StreamParticle[], userId: string | undefined) {
|
export function useDockBadge(networkId: string | undefined) {
|
||||||
|
const user = useAuthStore((s) => s.user);
|
||||||
|
const userId = user?.id;
|
||||||
|
|
||||||
|
const visibilityScopes = useMemo(() => {
|
||||||
|
const scopes: string[] = [];
|
||||||
|
if (userId) scopes.push(`human:${userId}`);
|
||||||
|
if (networkId) scopes.push(`network:${networkId}`);
|
||||||
|
return scopes;
|
||||||
|
}, [userId, networkId]);
|
||||||
|
|
||||||
|
const path = networkId ? particlePath(networkId, []) : undefined;
|
||||||
|
|
||||||
|
const { children } = useLiveParticleChildren(path, {
|
||||||
|
orderByField: "last_child_created_at",
|
||||||
|
orderDirection: "desc",
|
||||||
|
visibilityScopes,
|
||||||
|
whereFilter: openStatusFilter,
|
||||||
|
});
|
||||||
|
|
||||||
const unseenCount = useMemo(() => {
|
const unseenCount = useMemo(() => {
|
||||||
if (!userId) return 0;
|
if (!userId) return 0;
|
||||||
return streams.filter((s) => {
|
return children.filter((c): c is StreamParticle => {
|
||||||
const lastActivity = s.last_child_created_at?.getTime();
|
if (c.type !== "stream") return false;
|
||||||
|
const lastActivity = c.last_child_created_at?.getTime();
|
||||||
if (!lastActivity) return false;
|
if (!lastActivity) return false;
|
||||||
const marker = s.playback_markers?.[userId]?.getTime();
|
const marker = c.playback_markers?.[userId]?.getTime();
|
||||||
if (marker === undefined) return false;
|
if (marker === undefined) return false;
|
||||||
return lastActivity > marker;
|
return lastActivity > marker;
|
||||||
}).length;
|
}).length;
|
||||||
}, [streams, userId]);
|
}, [children, userId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electronApp.setDockBadge(unseenCount);
|
window.electronApp.setDockBadge(unseenCount);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ interface UseLiveParticleChildrenParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useLiveParticleChildren(
|
export function useLiveParticleChildren(
|
||||||
path: ParticlePath,
|
path: ParticlePath | undefined,
|
||||||
{
|
{
|
||||||
orderByField = "created_at",
|
orderByField = "created_at",
|
||||||
orderDirection = "desc",
|
orderDirection = "desc",
|
||||||
@@ -81,6 +81,12 @@ export function useLiveParticleChildren(
|
|||||||
const [error, setError] = useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!path) {
|
||||||
|
setChildren([]);
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setChildren([]);
|
setChildren([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user