mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Refactor sidebar components
This commit is contained in:
@@ -4,8 +4,6 @@ import { AccountListQueryHandler } from '@/main/handlers/queries/accounts-list';
|
||||
import { MessageListQueryHandler } from '@/main/handlers/queries/message-list';
|
||||
import { NodeGetQueryHandler } from '@/main/handlers/queries/node-get';
|
||||
import { ServerListQueryHandler } from '@/main/handlers/queries/server-list';
|
||||
import { SidebarChatListQueryHandler } from '@/main/handlers/queries/sidebar-chat-list';
|
||||
import { SidebarSpaceListQueryHandler } from '@/main/handlers/queries/sidebar-space-list';
|
||||
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';
|
||||
@@ -14,6 +12,7 @@ import { FileListQueryHandler } from '@/main/handlers/queries/file-list';
|
||||
import { EmojisGetQueryHandler } from '@/main/handlers/queries/emojis-get';
|
||||
import { IconsGetQueryHandler } from '@/main/handlers/queries/icons-get';
|
||||
import { NodeWithAncestorsGetQueryHandler } from '@/main/handlers/queries/node-with-ancestors-get';
|
||||
import { NodeChildrenGetQueryHandler } from '@/main/handlers/queries/node-children-get';
|
||||
|
||||
type QueryHandlerMap = {
|
||||
[K in keyof QueryMap]: QueryHandler<QueryMap[K]['input']>;
|
||||
@@ -25,8 +24,6 @@ export const queryHandlerMap: QueryHandlerMap = {
|
||||
node_get: new NodeGetQueryHandler(),
|
||||
record_list: new RecordListQueryHandler(),
|
||||
server_list: new ServerListQueryHandler(),
|
||||
sidebar_chat_list: new SidebarChatListQueryHandler(),
|
||||
sidebar_space_list: new SidebarSpaceListQueryHandler(),
|
||||
user_search: new UserSearchQueryHandler(),
|
||||
workspace_list: new WorkspaceListQueryHandler(),
|
||||
workspace_user_list: new WorkspaceUserListQueryHandler(),
|
||||
@@ -34,4 +31,5 @@ export const queryHandlerMap: QueryHandlerMap = {
|
||||
emojis_get: new EmojisGetQueryHandler(),
|
||||
icons_get: new IconsGetQueryHandler(),
|
||||
node_with_ancestors_get: new NodeWithAncestorsGetQueryHandler(),
|
||||
node_children_get: new NodeChildrenGetQueryHandler(),
|
||||
};
|
||||
|
||||
81
apps/desktop/src/main/handlers/queries/node-children-get.tsx
Normal file
81
apps/desktop/src/main/handlers/queries/node-children-get.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { NodeChildrenGetQueryInput } from '@/operations/queries/node-children-get';
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { mapNode } from '@/main/utils';
|
||||
import { SelectNode } from '@/main/data/workspace/schema';
|
||||
import {
|
||||
MutationChange,
|
||||
ChangeCheckResult,
|
||||
QueryHandler,
|
||||
QueryResult,
|
||||
} from '@/main/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
export class NodeChildrenGetQueryHandler
|
||||
implements QueryHandler<NodeChildrenGetQueryInput>
|
||||
{
|
||||
public async handleQuery(
|
||||
input: NodeChildrenGetQueryInput
|
||||
): Promise<QueryResult<NodeChildrenGetQueryInput>> {
|
||||
const rows = await this.fetchChildren(input);
|
||||
|
||||
return {
|
||||
output: rows.map(mapNode),
|
||||
state: {
|
||||
rows,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
changes: MutationChange[],
|
||||
input: NodeChildrenGetQueryInput,
|
||||
state: Record<string, any>
|
||||
): Promise<ChangeCheckResult<NodeChildrenGetQueryInput>> {
|
||||
if (
|
||||
!changes.some(
|
||||
(change) =>
|
||||
change.type === 'workspace' &&
|
||||
change.table === 'nodes' &&
|
||||
change.userId === input.userId
|
||||
)
|
||||
) {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
const rows = await this.fetchChildren(input);
|
||||
if (isEqual(rows, state.rows)) {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: {
|
||||
output: rows.map(mapNode),
|
||||
state: {
|
||||
rows,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchChildren(
|
||||
input: NodeChildrenGetQueryInput
|
||||
): Promise<SelectNode[]> {
|
||||
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const rows = await workspaceDatabase
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('parent_id', '=', input.nodeId)
|
||||
.where('type', 'in', input.types ?? [])
|
||||
.execute();
|
||||
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
import { SidebarChatListQueryInput } from '@/operations/queries/sidebar-chat-list';
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { SelectNode } from '@/main/data/workspace/schema';
|
||||
import { NodeTypes } from '@colanode/core';
|
||||
import { SidebarChatNode } from '@/types/workspaces';
|
||||
import { mapNode } from '@/main/utils';
|
||||
import {
|
||||
MutationChange,
|
||||
ChangeCheckResult,
|
||||
QueryHandler,
|
||||
QueryResult,
|
||||
} from '@/main/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
interface UnreadCountRow {
|
||||
node_id: string;
|
||||
unread_count: number;
|
||||
mentions_count: number;
|
||||
}
|
||||
|
||||
export class SidebarChatListQueryHandler
|
||||
implements QueryHandler<SidebarChatListQueryInput>
|
||||
{
|
||||
public async handleQuery(
|
||||
input: SidebarChatListQueryInput
|
||||
): Promise<QueryResult<SidebarChatListQueryInput>> {
|
||||
const chats = await this.fetchChats(input);
|
||||
const collaborators = await this.fetchChatCollaborators(input, chats);
|
||||
const unreadCounts = await this.fetchUnreadCounts(input, chats);
|
||||
|
||||
return {
|
||||
output: this.buildSidebarChatNodes(
|
||||
input.userId,
|
||||
chats,
|
||||
collaborators,
|
||||
unreadCounts
|
||||
),
|
||||
state: {
|
||||
chats,
|
||||
collaborators,
|
||||
unreadCounts,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
changes: MutationChange[],
|
||||
input: SidebarChatListQueryInput,
|
||||
state: Record<string, any>
|
||||
): Promise<ChangeCheckResult<SidebarChatListQueryInput>> {
|
||||
if (
|
||||
!changes.some(
|
||||
(change) =>
|
||||
change.type === 'workspace' &&
|
||||
(change.table === 'nodes' || change.table === 'user_nodes') &&
|
||||
change.userId === input.userId
|
||||
)
|
||||
) {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
const chats = await this.fetchChats(input);
|
||||
const collaborators = await this.fetchChatCollaborators(input, chats);
|
||||
const unreadCounts = await this.fetchUnreadCounts(input, chats);
|
||||
|
||||
if (
|
||||
isEqual(chats, state.chats) &&
|
||||
isEqual(collaborators, state.collaborators) &&
|
||||
isEqual(unreadCounts, state.unreadCounts)
|
||||
) {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: {
|
||||
output: this.buildSidebarChatNodes(
|
||||
input.userId,
|
||||
chats,
|
||||
collaborators,
|
||||
unreadCounts
|
||||
),
|
||||
state: {
|
||||
chats,
|
||||
collaborators,
|
||||
unreadCounts,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchChats(
|
||||
input: SidebarChatListQueryInput
|
||||
): Promise<SelectNode[]> {
|
||||
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const chats = await workspaceDatabase
|
||||
.selectFrom('nodes')
|
||||
.where('type', '=', NodeTypes.Chat)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
return chats;
|
||||
}
|
||||
|
||||
private async fetchChatCollaborators(
|
||||
input: SidebarChatListQueryInput,
|
||||
chats: SelectNode[]
|
||||
): Promise<SelectNode[]> {
|
||||
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const collaboratorIds: string[] = [];
|
||||
for (const chat of chats) {
|
||||
if (!chat.attributes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const attributes = JSON.parse(chat.attributes);
|
||||
if (!attributes.collaborators) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const keys = Object.keys(attributes.collaborators);
|
||||
for (const key of keys) {
|
||||
if (key === input.userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
collaboratorIds.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const collaborators = await workspaceDatabase
|
||||
.selectFrom('nodes')
|
||||
.where('id', 'in', collaboratorIds)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
return collaborators;
|
||||
}
|
||||
|
||||
private async fetchUnreadCounts(
|
||||
input: SidebarChatListQueryInput,
|
||||
chats: SelectNode[]
|
||||
): Promise<UnreadCountRow[]> {
|
||||
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const chatIds = chats.map((chat) => chat.id);
|
||||
const unreadCounts = await workspaceDatabase
|
||||
.selectFrom('user_nodes as un')
|
||||
.innerJoin('nodes as n', 'un.node_id', 'n.id')
|
||||
.where('un.user_id', '=', input.userId)
|
||||
.where('n.type', '=', NodeTypes.Message)
|
||||
.where('n.parent_id', 'in', chatIds)
|
||||
.where('un.last_seen_version_id', 'is', null)
|
||||
.select(['n.parent_id as node_id'])
|
||||
.select((eb) => [
|
||||
eb.fn.count<number>('un.node_id').as('unread_count'),
|
||||
eb.fn.sum<number>('un.mentions_count').as('mentions_count'),
|
||||
])
|
||||
.groupBy('n.parent_id')
|
||||
.execute();
|
||||
|
||||
return unreadCounts;
|
||||
}
|
||||
|
||||
private buildSidebarChatNodes = (
|
||||
userId: string,
|
||||
chats: SelectNode[],
|
||||
collaborators: SelectNode[],
|
||||
unreadCounts: UnreadCountRow[]
|
||||
): SidebarChatNode[] => {
|
||||
const sidebarChatNodes: SidebarChatNode[] = [];
|
||||
|
||||
for (const chat of chats) {
|
||||
const chatNode = mapNode(chat);
|
||||
if (chatNode.type !== 'chat') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!chatNode.attributes || !chatNode.attributes.collaborators) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const collaboratorIds = Object.keys(chatNode.attributes.collaborators);
|
||||
if (!collaboratorIds || collaboratorIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const collaboratorId = collaboratorIds.find((id) => id !== userId);
|
||||
|
||||
if (!collaboratorId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const collaboratorRow = collaborators.find(
|
||||
(r) => r.id === collaboratorId
|
||||
);
|
||||
|
||||
if (!collaboratorRow) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const collaboratorNode = mapNode(collaboratorRow);
|
||||
if (collaboratorNode.type !== 'user') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unreadCountRow = unreadCounts.find((r) => r.node_id === chat.id);
|
||||
|
||||
sidebarChatNodes.push({
|
||||
id: chatNode.id,
|
||||
type: chatNode.type,
|
||||
name: collaboratorNode.attributes.name ?? 'Unknown',
|
||||
avatar: collaboratorNode.attributes.avatar ?? null,
|
||||
unreadCount: unreadCountRow?.unread_count ?? 0,
|
||||
mentionsCount: unreadCountRow?.mentions_count ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
return sidebarChatNodes;
|
||||
};
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
import { SidebarSpaceListQueryInput } from '@/operations/queries/sidebar-space-list';
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { sql } from 'kysely';
|
||||
import { SelectNode } from '@/main/data/workspace/schema';
|
||||
import { NodeTypes } from '@colanode/core';
|
||||
import { SidebarNode, SidebarSpaceNode } from '@/types/workspaces';
|
||||
import { mapNode } from '@/main/utils';
|
||||
import { Node } from '@colanode/core';
|
||||
import {
|
||||
MutationChange,
|
||||
ChangeCheckResult,
|
||||
QueryHandler,
|
||||
QueryResult,
|
||||
} from '@/main/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { compareString } from '@/lib/utils';
|
||||
|
||||
interface UnreadCountRow {
|
||||
node_id: string;
|
||||
unread_count: number;
|
||||
mentions_count: number;
|
||||
}
|
||||
|
||||
export class SidebarSpaceListQueryHandler
|
||||
implements QueryHandler<SidebarSpaceListQueryInput>
|
||||
{
|
||||
public async handleQuery(
|
||||
input: SidebarSpaceListQueryInput
|
||||
): Promise<QueryResult<SidebarSpaceListQueryInput>> {
|
||||
const rows = await this.fetchNodes(input);
|
||||
const unreadCounts = await this.fetchUnreadCounts(input, rows);
|
||||
|
||||
return {
|
||||
output: this.buildSidebarSpaceNodes(rows, unreadCounts),
|
||||
state: {
|
||||
rows,
|
||||
unreadCounts,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
changes: MutationChange[],
|
||||
input: SidebarSpaceListQueryInput,
|
||||
state: Record<string, any>
|
||||
): Promise<ChangeCheckResult<SidebarSpaceListQueryInput>> {
|
||||
if (
|
||||
!changes.some(
|
||||
(change) =>
|
||||
change.type === 'workspace' &&
|
||||
(change.table === 'nodes' || change.table === 'user_nodes') &&
|
||||
change.userId === input.userId
|
||||
)
|
||||
) {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
const rows = await this.fetchNodes(input);
|
||||
const unreadCounts = await this.fetchUnreadCounts(input, rows);
|
||||
if (
|
||||
isEqual(rows, state.rows) &&
|
||||
isEqual(unreadCounts, state.unreadCounts)
|
||||
) {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: {
|
||||
output: this.buildSidebarSpaceNodes(rows, unreadCounts),
|
||||
state: {
|
||||
rows,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchNodes(
|
||||
input: SidebarSpaceListQueryInput
|
||||
): Promise<SelectNode[]> {
|
||||
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const query = sql<SelectNode>`
|
||||
WITH space_nodes AS (
|
||||
SELECT *
|
||||
FROM nodes
|
||||
WHERE type = ${NodeTypes.Space}
|
||||
),
|
||||
space_children_nodes AS (
|
||||
SELECT *
|
||||
FROM nodes
|
||||
WHERE parent_id IN (SELECT id FROM space_nodes)
|
||||
)
|
||||
SELECT * FROM space_nodes
|
||||
UNION ALL
|
||||
SELECT * FROM space_children_nodes;
|
||||
`.compile(workspaceDatabase);
|
||||
|
||||
const result = await workspaceDatabase.executeQuery(query);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
private async fetchUnreadCounts(
|
||||
input: SidebarSpaceListQueryInput,
|
||||
rows: SelectNode[]
|
||||
): Promise<UnreadCountRow[]> {
|
||||
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const channelIds = rows
|
||||
.filter((r) => r.type === NodeTypes.Channel)
|
||||
.map((r) => r.id);
|
||||
|
||||
const unreadCounts = await workspaceDatabase
|
||||
.selectFrom('user_nodes as un')
|
||||
.innerJoin('nodes as n', 'un.node_id', 'n.id')
|
||||
.where('un.user_id', '=', input.userId)
|
||||
.where('n.type', '=', NodeTypes.Message)
|
||||
.where('n.parent_id', 'in', channelIds)
|
||||
.where('un.last_seen_version_id', 'is', null)
|
||||
.select(['n.parent_id as node_id'])
|
||||
.select((eb) => [
|
||||
eb.fn.count<number>('un.node_id').as('unread_count'),
|
||||
eb.fn.sum<number>('un.mentions_count').as('mentions_count'),
|
||||
])
|
||||
.groupBy('n.parent_id')
|
||||
.execute();
|
||||
|
||||
return unreadCounts;
|
||||
}
|
||||
|
||||
private buildSidebarSpaceNodes = (
|
||||
rows: SelectNode[],
|
||||
unreadCounts: UnreadCountRow[]
|
||||
): SidebarSpaceNode[] => {
|
||||
const nodes: Node[] = rows.map(mapNode);
|
||||
const spaces: SidebarSpaceNode[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
if (node.type !== NodeTypes.Space) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const children = nodes
|
||||
.filter((n) => n.parentId === node.id)
|
||||
.sort((a, b) => compareString(a.index, b.index));
|
||||
|
||||
const spaceNode = this.buildSpaceNode(node, children, unreadCounts);
|
||||
if (spaceNode) {
|
||||
spaces.push(spaceNode);
|
||||
}
|
||||
}
|
||||
|
||||
return spaces;
|
||||
};
|
||||
|
||||
private buildSpaceNode = (
|
||||
node: Node,
|
||||
children: Node[],
|
||||
unreadCounts: UnreadCountRow[]
|
||||
): SidebarSpaceNode | null => {
|
||||
if (node.type !== 'space') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const childrenNodes: SidebarNode[] = [];
|
||||
for (const child of children) {
|
||||
const childNode = this.buildSidearNode(child, unreadCounts);
|
||||
if (childNode) {
|
||||
childrenNodes.push(childNode);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
name: node.attributes.name ?? null,
|
||||
avatar: node.attributes.avatar ?? null,
|
||||
children: childrenNodes,
|
||||
unreadCount: 0,
|
||||
mentionsCount: 0,
|
||||
};
|
||||
};
|
||||
|
||||
private buildSidearNode = (
|
||||
node: Node,
|
||||
unreadCounts: UnreadCountRow[]
|
||||
): SidebarNode | null => {
|
||||
const unreadCountRow = unreadCounts.find((r) => r.node_id === node.id);
|
||||
|
||||
if (
|
||||
node.type !== 'channel' &&
|
||||
node.type !== 'page' &&
|
||||
node.type !== 'folder' &&
|
||||
node.type !== 'database'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
name: node.attributes.name ?? null,
|
||||
avatar: node.attributes.avatar ?? null,
|
||||
unreadCount: unreadCountRow?.unread_count ?? 0,
|
||||
mentionsCount: unreadCountRow?.mentions_count ?? 0,
|
||||
};
|
||||
};
|
||||
}
|
||||
17
apps/desktop/src/operations/queries/node-children-get.tsx
Normal file
17
apps/desktop/src/operations/queries/node-children-get.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Node, NodeType } from '@colanode/core';
|
||||
|
||||
export type NodeChildrenGetQueryInput = {
|
||||
type: 'node_children_get';
|
||||
nodeId: string;
|
||||
userId: string;
|
||||
types?: NodeType[];
|
||||
};
|
||||
|
||||
declare module '@/operations/queries' {
|
||||
interface QueryMap {
|
||||
node_children_get: {
|
||||
input: NodeChildrenGetQueryInput;
|
||||
output: Node[];
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { SidebarChatNode } from '@/types/workspaces';
|
||||
|
||||
export type SidebarChatListQueryInput = {
|
||||
type: 'sidebar_chat_list';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/operations/queries' {
|
||||
interface QueryMap {
|
||||
sidebar_chat_list: {
|
||||
input: SidebarChatListQueryInput;
|
||||
output: SidebarChatNode[];
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { SidebarSpaceNode } from '@/types/workspaces';
|
||||
|
||||
export type SidebarSpaceListQueryInput = {
|
||||
type: 'sidebar_space_list';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/operations/queries' {
|
||||
interface QueryMap {
|
||||
sidebar_space_list: {
|
||||
input: SidebarSpaceListQueryInput;
|
||||
output: SidebarSpaceNode[];
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChannelNode } from '@colanode/core';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
|
||||
interface ChannelSidebarItemProps {
|
||||
node: ChannelNode;
|
||||
}
|
||||
|
||||
export const ChannelSidebarItem = ({ node }: ChannelSidebarItemProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const isActive = workspace.isNodeActive(node.id);
|
||||
const isUnread = false;
|
||||
const mentionsCount = 0;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={node.id}
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
isActive && 'bg-sidebar-accent'
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
id={node.id}
|
||||
avatar={node.attributes.avatar}
|
||||
name={node.attributes.name}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 w-full flex-grow pl-2 text-left',
|
||||
isUnread && 'font-bold'
|
||||
)}
|
||||
>
|
||||
{node.attributes.name ?? 'Unnamed'}
|
||||
</span>
|
||||
{mentionsCount > 0 && (
|
||||
<span className="mr-1 rounded-md bg-sidebar-accent px-1 py-0.5 text-xs text-sidebar-accent-foreground">
|
||||
{mentionsCount}
|
||||
</span>
|
||||
)}
|
||||
{mentionsCount == 0 && isUnread && (
|
||||
<span className="size-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChatNode } from '@colanode/core';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
import { useQuery } from '@/renderer/hooks/use-query';
|
||||
|
||||
interface ChatSidebarItemProps {
|
||||
node: ChatNode;
|
||||
}
|
||||
|
||||
export const ChatSidebarItem = ({ node }: ChatSidebarItemProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const collaboratorId =
|
||||
Object.keys(node.attributes.collaborators).find(
|
||||
(id) => id !== workspace.userId
|
||||
) ?? '';
|
||||
|
||||
const { data, isPending } = useQuery({
|
||||
type: 'node_get',
|
||||
nodeId: collaboratorId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (isPending || !data || data.type !== 'user') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isActive = workspace.isNodeActive(node.id);
|
||||
const isUnread = false;
|
||||
const mentionsCount = 0;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={node.id}
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
isActive && 'bg-sidebar-accent'
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
id={data.id}
|
||||
avatar={data.attributes.avatar}
|
||||
name={data.attributes.name}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 w-full flex-grow pl-2 text-left',
|
||||
isUnread && 'font-bold'
|
||||
)}
|
||||
>
|
||||
{data.attributes.name ?? 'Unnamed'}
|
||||
</span>
|
||||
{mentionsCount > 0 && (
|
||||
<span className="mr-1 rounded-md bg-sidebar-accent px-1 py-0.5 text-xs text-sidebar-accent-foreground">
|
||||
{mentionsCount}
|
||||
</span>
|
||||
)}
|
||||
{mentionsCount == 0 && isUnread && (
|
||||
<span className="size-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DatabaseNode } from '@colanode/core';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
|
||||
interface DatabaseSidebarItemProps {
|
||||
node: DatabaseNode;
|
||||
}
|
||||
|
||||
export const DatabaseSidebarItem = ({ node }: DatabaseSidebarItemProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const isActive = workspace.isNodeActive(node.id);
|
||||
const isUnread = false;
|
||||
const mentionsCount = 0;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={node.id}
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
isActive && 'bg-sidebar-accent'
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
id={node.id}
|
||||
avatar={node.attributes.avatar}
|
||||
name={node.attributes.name}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 w-full flex-grow pl-2 text-left',
|
||||
isUnread && 'font-bold'
|
||||
)}
|
||||
>
|
||||
{node.attributes.name ?? 'Unnamed'}
|
||||
</span>
|
||||
{mentionsCount > 0 && (
|
||||
<span className="mr-1 rounded-md bg-sidebar-accent px-1 py-0.5 text-xs text-sidebar-accent-foreground">
|
||||
{mentionsCount}
|
||||
</span>
|
||||
)}
|
||||
{mentionsCount == 0 && isUnread && (
|
||||
<span className="size-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { FolderNode } from '@colanode/core';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
|
||||
interface FolderSidebarItemProps {
|
||||
node: FolderNode;
|
||||
}
|
||||
|
||||
export const FolderSidebarItem = ({ node }: FolderSidebarItemProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const isActive = workspace.isNodeActive(node.id);
|
||||
const isUnread = false;
|
||||
const mentionsCount = 0;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={node.id}
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
isActive && 'bg-sidebar-accent'
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
id={node.id}
|
||||
avatar={node.attributes.avatar}
|
||||
name={node.attributes.name}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 w-full flex-grow pl-2 text-left',
|
||||
isUnread && 'font-bold'
|
||||
)}
|
||||
>
|
||||
{node.attributes.name ?? 'Unnamed'}
|
||||
</span>
|
||||
{mentionsCount > 0 && (
|
||||
<span className="mr-1 rounded-md bg-sidebar-accent px-1 py-0.5 text-xs text-sidebar-accent-foreground">
|
||||
{mentionsCount}
|
||||
</span>
|
||||
)}
|
||||
{mentionsCount == 0 && isUnread && (
|
||||
<span className="size-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PageNode } from '@colanode/core';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
|
||||
interface PageSidebarItemProps {
|
||||
node: PageNode;
|
||||
}
|
||||
|
||||
export const PageSidebarItem = ({ node }: PageSidebarItemProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const isActive = workspace.isNodeActive(node.id);
|
||||
const isUnread = false;
|
||||
const mentionsCount = 0;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={node.id}
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
isActive && 'bg-sidebar-accent'
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
id={node.id}
|
||||
avatar={node.attributes.avatar}
|
||||
name={node.attributes.name}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 w-full flex-grow pl-2 text-left',
|
||||
isUnread && 'font-bold'
|
||||
)}
|
||||
>
|
||||
{node.attributes.name ?? 'Unnamed'}
|
||||
</span>
|
||||
{mentionsCount > 0 && (
|
||||
<span className="mr-1 rounded-md bg-sidebar-accent px-1 py-0.5 text-xs text-sidebar-accent-foreground">
|
||||
{mentionsCount}
|
||||
</span>
|
||||
)}
|
||||
{mentionsCount == 0 && isUnread && (
|
||||
<span className="size-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { SpaceNode } from '@colanode/core';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
import { ChannelCreateDialog } from '@/renderer/components/channels/channel-create-dialog';
|
||||
import { PageCreateDialog } from '@/renderer/components/pages/page-create-dialog';
|
||||
import { DatabaseCreateDialog } from '@/renderer/components/databases/database-create-dialog';
|
||||
import { SidebarSpaceNode } from '@/types/workspaces';
|
||||
import { SidebarItem } from '@/renderer/components/workspaces/sidebars/sidebar-item';
|
||||
import {
|
||||
Collapsible,
|
||||
@@ -39,20 +39,28 @@ import {
|
||||
Plus,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useQuery } from '@/renderer/hooks/use-query';
|
||||
|
||||
interface SettingsState {
|
||||
open: boolean;
|
||||
tab?: string;
|
||||
}
|
||||
|
||||
interface SidebarSpaceNodeProps {
|
||||
node: SidebarSpaceNode;
|
||||
interface SpaceSidebarItemProps {
|
||||
node: SpaceNode;
|
||||
}
|
||||
|
||||
export const SidebarSpaceItem = ({ node }: SidebarSpaceNodeProps) => {
|
||||
export const SpaceSidebarItem = ({ node }: SpaceSidebarItemProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const { nodeId } = useParams<{ nodeId?: string }>();
|
||||
|
||||
const { data } = useQuery({
|
||||
type: 'node_children_get',
|
||||
nodeId: node.id,
|
||||
userId: workspace.userId,
|
||||
types: ['page', 'channel', 'database', 'folder'],
|
||||
});
|
||||
|
||||
const children = data ?? [];
|
||||
|
||||
const [openCreatePage, setOpenCreatePage] = React.useState(false);
|
||||
const [openCreateChannel, setOpenCreateChannel] = React.useState(false);
|
||||
@@ -73,18 +81,17 @@ export const SidebarSpaceItem = ({ node }: SidebarSpaceNodeProps) => {
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
isActive={nodeId === node.id}
|
||||
tooltip={node.name ?? ''}
|
||||
tooltip={node.attributes.name ?? ''}
|
||||
className="group/space-button"
|
||||
>
|
||||
<Avatar
|
||||
id={node.id}
|
||||
avatar={node.avatar}
|
||||
name={node.name}
|
||||
avatar={node.attributes.avatar}
|
||||
name={node.attributes.name}
|
||||
className="size-4 group-hover/space-button:hidden"
|
||||
/>
|
||||
<ChevronRight className="hidden size-4 transition-transform duration-200 group-hover/space-button:block group-data-[state=open]/collapsible:rotate-90" />
|
||||
<span>{node.name}</span>
|
||||
<span>{node.attributes.name}</span>
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<DropdownMenu>
|
||||
@@ -97,7 +104,9 @@ export const SidebarSpaceItem = ({ node }: SidebarSpaceNodeProps) => {
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="ml-1 w-72">
|
||||
<DropdownMenuLabel>{node.name ?? 'Unnamed'}</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{node.attributes.name ?? 'Unnamed'}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSelect={() => setOpenCreatePage(true)}>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
@@ -150,7 +159,7 @@ export const SidebarSpaceItem = ({ node }: SidebarSpaceNodeProps) => {
|
||||
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub className="mr-0 pr-0">
|
||||
{node.children.map((child) => (
|
||||
{children.map((child) => (
|
||||
<SidebarMenuSubItem
|
||||
key={child.id}
|
||||
onClick={() => {
|
||||
@@ -158,8 +167,10 @@ export const SidebarSpaceItem = ({ node }: SidebarSpaceNodeProps) => {
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<SidebarMenuSubButton isActive={nodeId === child.id}>
|
||||
<SidebarItem node={child} isActive={nodeId === child.id} />
|
||||
<SidebarMenuSubButton
|
||||
isActive={workspace.isNodeActive(child.id)}
|
||||
>
|
||||
<SidebarItem node={child} />
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
@@ -198,8 +209,8 @@ export const SidebarSpaceItem = ({ node }: SidebarSpaceNodeProps) => {
|
||||
{settingsState.open && (
|
||||
<SpaceSettingsDialog
|
||||
id={node.id}
|
||||
name={node.name}
|
||||
avatar={node.avatar}
|
||||
name={node.attributes.name}
|
||||
avatar={node.attributes.avatar}
|
||||
open={settingsState.open}
|
||||
onOpenChange={(open) =>
|
||||
setSettingsState({ open, tab: settingsState.tab })
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import { SidebarChatNode } from '@/types/workspaces';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
|
||||
interface SidebarChatItemProps {
|
||||
node: SidebarChatNode;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export const SidebarChatItem = ({
|
||||
node,
|
||||
isActive,
|
||||
}: SidebarChatItemProps): React.ReactNode => {
|
||||
const isUnread =
|
||||
!isActive && (node.unreadCount > 0 || node.mentionsCount > 0);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={node.id}
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
isActive && 'bg-sidebar-accent'
|
||||
)}
|
||||
>
|
||||
<Avatar id={node.id} avatar={node.avatar} name={node.name} size="small" />
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 w-full flex-grow pl-2 text-left',
|
||||
isUnread && 'font-bold'
|
||||
)}
|
||||
>
|
||||
{node.name ?? 'Unnamed'}
|
||||
</span>
|
||||
{node.unreadCount > 0 && (
|
||||
<span className="rounded-md bg-red-500 px-1 py-0.5 text-xs text-white">
|
||||
{node.unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ChatCreatePopover } from '@/renderer/components/chats/chat-create-popover';
|
||||
import { useQuery } from '@/renderer/hooks/use-query';
|
||||
import { SidebarChatItem } from '@/renderer/components/workspaces/sidebars/sidebar-chat-item';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
|
||||
import {
|
||||
@@ -11,17 +10,21 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { ChatNode } from '@colanode/core';
|
||||
import { ChatSidebarItem } from '@/renderer/components/chats/chat-sidebar-item';
|
||||
|
||||
export const SidebarChats = () => {
|
||||
const workspace = useWorkspace();
|
||||
const { nodeId } = useParams<{ nodeId?: string }>();
|
||||
|
||||
const { data } = useQuery({
|
||||
type: 'sidebar_chat_list',
|
||||
type: 'node_children_get',
|
||||
userId: workspace.userId,
|
||||
nodeId: workspace.id,
|
||||
types: ['chat'],
|
||||
});
|
||||
|
||||
const chats = data?.map((node) => node as ChatNode) ?? [];
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group/sidebar-chats group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Chats</SidebarGroupLabel>
|
||||
@@ -29,15 +32,15 @@ export const SidebarChats = () => {
|
||||
<ChatCreatePopover />
|
||||
</SidebarGroupAction>
|
||||
<SidebarMenu>
|
||||
{data?.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
{chats.map((item) => (
|
||||
<SidebarMenuItem key={item.id}>
|
||||
<SidebarMenuButton
|
||||
isActive={nodeId === item.id}
|
||||
isActive={workspace.isNodeActive(item.id)}
|
||||
onClick={() => {
|
||||
workspace.navigateToNode(item.id);
|
||||
}}
|
||||
>
|
||||
<SidebarChatItem node={item} isActive={nodeId === item.id} />
|
||||
<ChatSidebarItem node={item} />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
|
||||
@@ -1,50 +1,31 @@
|
||||
import React from 'react';
|
||||
import { SidebarNode } from '@/types/workspaces';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||
import { Node } from '@colanode/core';
|
||||
import { SpaceSidebarItem } from '@/renderer/components/spaces/space-sidebar-item';
|
||||
import { ChannelSidebarItem } from '@/renderer/components/channels/channel-sidebar-item';
|
||||
import { ChatSidebarItem } from '@/renderer/components/chats/chat-sidebar-item';
|
||||
import { PageSidebarItem } from '@/renderer/components/pages/page-sidebar-item';
|
||||
import { DatabaseSidebarItem } from '@/renderer/components/databases/database-sidiebar-item';
|
||||
import { FolderSidebarItem } from '@/renderer/components/folders/folder-sidebar-item';
|
||||
|
||||
interface SidebarItemProps {
|
||||
node: SidebarNode;
|
||||
isActive?: boolean;
|
||||
node: Node;
|
||||
}
|
||||
|
||||
export const SidebarItem = ({
|
||||
node,
|
||||
isActive,
|
||||
}: SidebarItemProps): React.ReactNode => {
|
||||
const isUnread =
|
||||
!isActive && (node.unreadCount > 0 || node.mentionsCount > 0);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={node.id}
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
isActive && 'bg-sidebar-accent'
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
id={node.id}
|
||||
avatar={node.avatar}
|
||||
name={node.name}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'line-clamp-1 w-full flex-grow pl-2 text-left',
|
||||
isUnread && 'font-bold'
|
||||
)}
|
||||
>
|
||||
{node.name ?? 'Unnamed'}
|
||||
</span>
|
||||
{node.mentionsCount > 0 && (
|
||||
<span className="mr-1 rounded-md bg-sidebar-accent px-1 py-0.5 text-xs text-sidebar-accent-foreground">
|
||||
{node.mentionsCount}
|
||||
</span>
|
||||
)}
|
||||
{node.mentionsCount == 0 && isUnread && (
|
||||
<span className="size-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
export const SidebarItem = ({ node }: SidebarItemProps): React.ReactNode => {
|
||||
switch (node.type) {
|
||||
case 'space':
|
||||
return <SpaceSidebarItem node={node} />;
|
||||
case 'channel':
|
||||
return <ChannelSidebarItem node={node} />;
|
||||
case 'chat':
|
||||
return <ChatSidebarItem node={node} />;
|
||||
case 'page':
|
||||
return <PageSidebarItem node={node} />;
|
||||
case 'database':
|
||||
return <DatabaseSidebarItem node={node} />;
|
||||
case 'folder':
|
||||
return <FolderSidebarItem node={node} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SidebarSpaceItem } from '@/renderer/components/workspaces/sidebars/sidebar-space-item';
|
||||
import { useQuery } from '@/renderer/hooks/use-query';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
import {
|
||||
@@ -8,6 +7,8 @@ import {
|
||||
SidebarMenu,
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
import { SpaceCreateButton } from '@/renderer/components/spaces/space-create-button';
|
||||
import { SpaceNode } from '@colanode/core';
|
||||
import { SpaceSidebarItem } from '@/renderer/components/spaces/space-sidebar-item';
|
||||
|
||||
export const SidebarSpaces = () => {
|
||||
const workspace = useWorkspace();
|
||||
@@ -15,10 +16,14 @@ export const SidebarSpaces = () => {
|
||||
workspace.role !== 'guest' && workspace.role !== 'none';
|
||||
|
||||
const { data } = useQuery({
|
||||
type: 'sidebar_space_list',
|
||||
type: 'node_children_get',
|
||||
userId: workspace.userId,
|
||||
nodeId: workspace.id,
|
||||
types: ['space'],
|
||||
});
|
||||
|
||||
const spaces = data?.map((node) => node as SpaceNode) ?? [];
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group/sidebar-spaces">
|
||||
<SidebarGroupLabel>Spaces</SidebarGroupLabel>
|
||||
@@ -28,7 +33,9 @@ export const SidebarSpaces = () => {
|
||||
</SidebarGroupAction>
|
||||
)}
|
||||
<SidebarMenu>
|
||||
{data?.map((space) => <SidebarSpaceItem node={space} key={space.id} />)}
|
||||
{spaces.map((space) => (
|
||||
<SpaceSidebarItem node={space} key={space.id} />
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
|
||||
export const Workspace = () => {
|
||||
const { userId } = useParams<{ userId: string }>();
|
||||
const { userId, nodeId } = useParams<{ userId: string; nodeId?: string }>();
|
||||
|
||||
const account = useAccount();
|
||||
const navigate = useNavigate();
|
||||
@@ -36,6 +36,9 @@ export const Workspace = () => {
|
||||
navigateToNode(nodeId) {
|
||||
navigate(`/${userId}/${nodeId}`);
|
||||
},
|
||||
isNodeActive(id) {
|
||||
return id === nodeId;
|
||||
},
|
||||
openModal(modal) {
|
||||
setSearchParams((prev) => {
|
||||
return {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Workspace } from '@/types/workspaces';
|
||||
|
||||
interface WorkspaceContext extends Workspace {
|
||||
navigateToNode: (nodeId: string) => void;
|
||||
isNodeActive: (nodeId: string) => boolean;
|
||||
openModal: (nodeId: string) => void;
|
||||
openSettings: () => void;
|
||||
markAsSeen: (nodeId: string, versionId: string) => void;
|
||||
|
||||
@@ -11,35 +11,6 @@ export type Workspace = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export type SidebarNode = {
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
avatar: string | null;
|
||||
unreadCount: number;
|
||||
mentionsCount: number;
|
||||
};
|
||||
|
||||
export type SidebarSpaceNode = SidebarNode & {
|
||||
children: SidebarNode[];
|
||||
};
|
||||
|
||||
export type SidebarChatNode = {
|
||||
id: string;
|
||||
type: string;
|
||||
name: string | null;
|
||||
avatar: string | null;
|
||||
unreadCount: number;
|
||||
mentionsCount: number;
|
||||
};
|
||||
|
||||
export type BreadcrumbNode = {
|
||||
id: string;
|
||||
type: string;
|
||||
name: string | null;
|
||||
avatar: string | null;
|
||||
};
|
||||
|
||||
export type WorkspaceCredentials = {
|
||||
workspaceId: string;
|
||||
accountId: string;
|
||||
|
||||
Reference in New Issue
Block a user