Display account read state indicator

This commit is contained in:
Hakan Shehu
2024-11-14 10:57:45 +01:00
parent 6537d51fdb
commit d405ac0f21
3 changed files with 109 additions and 34 deletions

View File

@@ -4,14 +4,13 @@ import { WorkspaceDatabaseSchema } from '@/main/data/workspace/schema';
import { getIdType, IdType, NodeTypes } from '@colanode/core';
import { WorkspaceReadState } from '@/shared/types/radars';
import { eventBus } from '@/shared/lib/event-bus';
import { Event } from '@/shared/types/events';
class RadarService {
private readonly workspaceStates: Record<string, WorkspaceReadState> = {};
constructor() {
eventBus.subscribe((event) => {
console.log('event', event);
});
eventBus.subscribe(this.handleEvent.bind(this));
}
public async init(): Promise<void> {
@@ -88,6 +87,18 @@ class RadarService {
this.workspaceStates[userId] = workspaceState;
}
private async handleEvent(event: Event) {
if (event.type === 'workspace_deleted') {
delete this.workspaceStates[event.workspace.userId];
} else if (event.type === 'user_node_created') {
// to be optimized
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
event.userId
);
await this.initWorkspace(event.userId, workspaceDatabase);
}
}
}
export const radarService = new RadarService();

View File

@@ -18,17 +18,70 @@ import { useAccount } from '@/renderer/contexts/account';
import { ChevronsUpDown, LogOut, Plus, Settings } from 'lucide-react';
import { useApp } from '@/renderer/contexts/app';
import { useNavigate } from 'react-router-dom';
import { useRadar } from '@/renderer/contexts/radar';
interface ReadStateIndicatorProps {
importantCount: number;
hasUnseenChanges: boolean;
}
const ReadStateIndicator = ({
importantCount,
hasUnseenChanges,
}: ReadStateIndicatorProps) => {
return (
<React.Fragment>
{importantCount > 0 && (
<span className="mr-1 rounded-md px-1 py-0.5 text-xs bg-red-400 text-white">
{importantCount}
</span>
)}
{importantCount === 0 && hasUnseenChanges && (
<span className="size-2 rounded-full bg-red-500" />
)}
</React.Fragment>
);
};
export function LayoutSidebarFooter() {
const app = useApp();
const account = useAccount();
const sidebar = useSidebar();
const navigate = useNavigate();
const radar = useRadar();
const [open, setOpen] = React.useState(false);
const otherAccounts = app.accounts.filter((a) => a.id !== account.id);
const accountStates: Record<string, ReadStateIndicatorProps> = {};
for (const otherAccount of otherAccounts) {
const accountWorkspaces = app.workspaces.filter(
(w) => w.accountId === otherAccount.id
);
const state: ReadStateIndicatorProps = {
importantCount: 0,
hasUnseenChanges: false,
};
for (const accountWorkspace of accountWorkspaces) {
const workspaceState = radar.getWorkspaceState(accountWorkspace.userId);
state.importantCount += workspaceState.importantCount;
state.hasUnseenChanges =
state.hasUnseenChanges || workspaceState.hasUnseenChanges;
}
accountStates[otherAccount.id] = state;
}
const importantCount = Object.values(accountStates).reduce(
(acc, curr) => acc + curr.importantCount,
0
);
const hasUnseenChanges = Object.values(accountStates).some(
(state) => state.hasUnseenChanges
);
return (
<SidebarMenu>
<SidebarMenuItem>
@@ -49,6 +102,10 @@ export function LayoutSidebarFooter() {
<span className="truncate text-xs">{account.email}</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
<ReadStateIndicator
importantCount={importantCount}
hasUnseenChanges={hasUnseenChanges}
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
@@ -96,32 +153,39 @@ export function LayoutSidebarFooter() {
<DropdownMenuLabel className="mb-1">
Other accounts
</DropdownMenuLabel>
{otherAccounts.map((otherAccount) => (
<DropdownMenuItem
key={otherAccount.id}
className="p-0"
onClick={() => {
navigate(`/${otherAccount.id}`);
}}
>
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar
className="h-8 w-8 rounded-lg"
id={otherAccount.id}
name={otherAccount.name}
avatar={otherAccount.avatar}
/>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{otherAccount.name}
</span>
<span className="truncate text-xs">
{otherAccount.email}
</span>
{otherAccounts.map((otherAccount) => {
const state = accountStates[otherAccount.id];
return (
<DropdownMenuItem
key={otherAccount.id}
className="p-0"
onClick={() => {
navigate(`/${otherAccount.id}`);
}}
>
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar
className="h-8 w-8 rounded-lg"
id={otherAccount.id}
name={otherAccount.name}
avatar={otherAccount.avatar}
/>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{otherAccount.name}
</span>
<span className="truncate text-xs">
{otherAccount.email}
</span>
</div>
<ReadStateIndicator
importantCount={state.importantCount}
hasUnseenChanges={state.hasUnseenChanges}
/>
</div>
</div>
</DropdownMenuItem>
))}
</DropdownMenuItem>
);
})}
</React.Fragment>
)}

View File

@@ -20,15 +20,15 @@ import {
import { ChevronsUpDown, Settings, Plus, Bell } from 'lucide-react';
import { useRadar } from '@/renderer/contexts/radar';
interface WorkspaceStateIndicatorProps {
interface ReadStateIndicatorProps {
importantCount: number;
hasUnseenChanges: boolean;
}
const WorkspaceStateIndicator = ({
const ReadStateIndicator = ({
importantCount,
hasUnseenChanges,
}: WorkspaceStateIndicatorProps) => {
}: ReadStateIndicatorProps) => {
return (
<React.Fragment>
{importantCount > 0 && (
@@ -85,7 +85,7 @@ export const LayoutSidebarHeader = () => {
<span className="truncate text-xs">Free Plan</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
<WorkspaceStateIndicator
<ReadStateIndicator
importantCount={importantCount}
hasUnseenChanges={hasUnseenChanges}
/>
@@ -152,7 +152,7 @@ export const LayoutSidebarHeader = () => {
<p className="flex-1 text-left text-sm leading-tight truncate font-normal">
{otherWorkspace.name}
</p>
<WorkspaceStateIndicator
<ReadStateIndicator
importantCount={workspaceState.importantCount}
hasUnseenChanges={workspaceState.hasUnseenChanges}
/>