mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Implement chat container and breadcrumb
This commit is contained in:
@@ -77,7 +77,7 @@ export class ChatCreateMutationHandler
|
||||
version_id: generateId(IdType.Version),
|
||||
},
|
||||
])
|
||||
.compile();
|
||||
.execute();
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
113
desktop/src/main/handlers/queries/chat-get.ts
Normal file
113
desktop/src/main/handlers/queries/chat-get.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
16
desktop/src/operations/queries/chat-get.ts
Normal file
16
desktop/src/operations/queries/chat-get.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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} />;
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
5
desktop/src/types/chats.ts
Normal file
5
desktop/src/types/chats.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type ChatNode = {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
};
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user