Implement chat container and breadcrumb

This commit is contained in:
Hakan Shehu
2024-10-07 22:52:50 +02:00
parent 6bb0296763
commit d833063978
12 changed files with 201 additions and 8 deletions

View File

@@ -77,7 +77,7 @@ export class ChatCreateMutationHandler
version_id: generateId(IdType.Version),
},
])
.compile();
.execute();
});
return {

View File

@@ -0,0 +1,113 @@
import { ChatGetQueryInput } from '@/operations/queries/chat-get';
import { databaseManager } from '@/main/data/database-manager';
import {
ChangeCheckResult,
QueryHandler,
QueryResult,
} from '@/operations/queries';
import { mapNode } from '@/lib/nodes';
import { sql } from 'kysely';
import { SelectNode } from '@/main/data/workspace/schema';
import { MutationChange } from '@/operations/mutations';
import { isEqual } from 'lodash';
import { ChatNode } from '@/types/chats';
export class ChatGetQueryHandler implements QueryHandler<ChatGetQueryInput> {
async handleQuery(
input: ChatGetQueryInput,
): Promise<QueryResult<ChatGetQueryInput>> {
const rows = await this.fetchNodes(input);
return {
output: this.buildChat(input.chatId, rows),
state: {
rows,
},
};
}
async checkForChanges(
changes: MutationChange[],
input: ChatGetQueryInput,
state: Record<string, any>,
): Promise<ChangeCheckResult<ChatGetQueryInput>> {
if (
!changes.some(
(change) =>
change.type === 'workspace' &&
(change.table === 'nodes' || change.table === 'node_collaborators') &&
change.userId === input.userId,
)
) {
return {
hasChanges: false,
};
}
const rows = await this.fetchNodes(input);
if (isEqual(rows, state.rows)) {
return {
hasChanges: false,
};
}
return {
hasChanges: true,
result: {
output: this.buildChat(input.chatId, rows),
state: {
rows,
},
},
};
}
private async fetchNodes(input: ChatGetQueryInput): Promise<SelectNode[]> {
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
input.userId,
);
const query = sql<SelectNode>`
WITH chat_node AS (
SELECT *
FROM nodes
WHERE id = ${input.chatId}
),
collaborator_nodes AS (
SELECT *
FROM nodes
WHERE id IN
(
SELECT DISTINCT collaborator_id
FROM node_collaborators
WHERE node_id = ${input.chatId} AND collaborator_id != ${input.userId}
)
)
SELECT * FROM chat_node
UNION ALL
SELECT * FROM collaborator_nodes
`.compile(workspaceDatabase);
const result = await workspaceDatabase.executeQuery(query);
return result.rows;
}
private buildChat = (chatId: string, rows: SelectNode[]): ChatNode | null => {
const nodes = rows.map(mapNode);
const chatNode = nodes.find((node) => node.id === chatId);
if (!chatNode) {
return null;
}
const collaborators = rows.filter((node) => node.id !== chatId);
if (collaborators.length === 0) {
return null;
}
const collaborator = mapNode(collaborators[0]);
return {
id: chatNode.id,
name: collaborator.attributes.name ?? 'Unknown',
avatar: collaborator.attributes.avatar ?? null,
};
};
}

View File

@@ -16,6 +16,7 @@ import { UserSearchQueryHandler } from '@/main/handlers/queries/user-search';
import { WorkspaceListQueryHandler } from '@/main/handlers/queries/workspace-list';
import { WorkspaceUserListQueryHandler } from '@/main/handlers/queries/workspace-user-list';
import { RecordListQueryHandler } from '@/main/handlers/queries/record-list';
import { ChatGetQueryHandler } from '@/main/handlers/queries/chat-get';
type QueryHandlerMap = {
[K in keyof QueryMap]: QueryHandler<QueryMap[K]['input']>;
@@ -39,4 +40,5 @@ export const queryHandlerMap: QueryHandlerMap = {
user_search: new UserSearchQueryHandler(),
workspace_list: new WorkspaceListQueryHandler(),
workspace_user_list: new WorkspaceUserListQueryHandler(),
chat_get: new ChatGetQueryHandler(),
};

View File

@@ -0,0 +1,16 @@
import { ChatNode } from '@/types/chats';
export type ChatGetQueryInput = {
type: 'chat_get';
chatId: string;
userId: string;
};
declare module '@/operations/queries' {
interface QueryMap {
chat_get: {
input: ChatGetQueryInput;
output: ChatNode;
};
}
}

View File

@@ -0,0 +1,10 @@
import React from 'react';
import { Conversation } from '@/renderer/components/messages/conversation';
interface ChatContainerNodeProps {
nodeId: string;
}
export const ChatContainerNode = ({ nodeId }: ChatContainerNodeProps) => {
return <Conversation conversationId={nodeId} />;
};

View File

@@ -12,11 +12,6 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/renderer/components/ui/dropdown-menu';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/renderer/components/ui/popover';
import { NodeTypes } from '@/lib/constants';
import { useWorkspace } from '@/renderer/contexts/workspace';
import { BreadcrumbItem } from '@/renderer/components/workspaces/containers/breadcrumb-item';

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { useWorkspace } from '@/renderer/contexts/workspace';
import { useQuery } from '@/renderer/hooks/use-query';
import {
Breadcrumb as BreadcrumbWrapper,
BreadcrumbItem as BreadcrumbItemWrapper,
BreadcrumbList,
} from '@/renderer/components/ui/breadcrumb';
import { BreadcrumbItem } from '@/renderer/components/workspaces/containers/breadcrumb-item';
interface ChatBreadcrumbProps {
chatId: string;
}
export const ChatBreadcrumb = ({ chatId }: ChatBreadcrumbProps) => {
const workspace = useWorkspace();
const { data, isPending } = useQuery({
type: 'chat_get',
chatId,
userId: workspace.userId,
});
if (isPending || !data) {
return null;
}
return (
<BreadcrumbWrapper>
<BreadcrumbList>
<BreadcrumbItemWrapper>
<BreadcrumbItem
node={{
id: data.id,
type: 'chat',
name: data.name,
avatar: data.avatar,
}}
/>
</BreadcrumbItemWrapper>
</BreadcrumbList>
</BreadcrumbWrapper>
);
};

View File

@@ -1,5 +1,7 @@
import React from 'react';
import { getIdType, IdType } from '@/lib/id';
import { Breadcrumb } from '@/renderer/components/workspaces/containers/breadcrumb';
import { ChatBreadcrumb } from '@/renderer/components/workspaces/containers/chat-breadcrumb';
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
interface ContainerHeaderProps {
@@ -7,9 +9,14 @@ interface ContainerHeaderProps {
}
export const ContainerHeader = ({ nodeId }: ContainerHeaderProps) => {
const idType = getIdType(nodeId);
return (
<div className="mx-1 flex h-12 items-center justify-between p-2 pr-4 text-foreground/80">
<Breadcrumb nodeId={nodeId} />
{idType === IdType.Chat ? (
<ChatBreadcrumb chatId={nodeId} />
) : (
<Breadcrumb nodeId={nodeId} />
)}
<NodeCollaboratorsPopover nodeId={nodeId} />
</div>
);

View File

@@ -6,6 +6,7 @@ import { ChannelContainerNode } from '@/renderer/components/channels/channel-con
import { ContainerHeader } from '@/renderer/components/workspaces/containers/container-header';
import { DatabaseContainerNode } from '@/renderer/components/databases/database-container-node';
import { RecordContainerNode } from '@/renderer/components/records/record-container-node';
import { ChatContainerNode } from '@/renderer/components/chats/chat-container-node';
import { useWorkspace } from '@/renderer/contexts/workspace';
import { getIdType, IdType } from '@/lib/id';
@@ -25,6 +26,7 @@ export const Container = () => {
.with(IdType.Page, () => <PageContainerNode nodeId={nodeId} />)
.with(IdType.Database, () => <DatabaseContainerNode nodeId={nodeId} />)
.with(IdType.Record, () => <RecordContainerNode nodeId={nodeId} />)
.with(IdType.Chat, () => <ChatContainerNode nodeId={nodeId} />)
.otherwise(() => null)}
</div>
);

View File

@@ -0,0 +1,5 @@
export type ChatNode = {
id: string;
name: string;
avatar: string;
};

View File

@@ -48,7 +48,7 @@ class SynapseManager {
const account = result.account;
socket.on('message', (message) => {
this.handleMessage(account.deviceId, message.toString());
this.handleMessage(account, message.toString());
});
socket.on('close', () => {