mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 03:37:51 +01:00
Use tanstackdb for some node queries
This commit is contained in:
@@ -25,8 +25,6 @@ import { IconListQueryHandler } from './icons/icon-list';
|
||||
import { IconSearchQueryHandler } from './icons/icon-search';
|
||||
import { IconSvgGetQueryHandler } from './icons/icon-svg-get';
|
||||
import { RadarDataGetQueryHandler } from './interactions/radar-data-get';
|
||||
import { NodeChildrenGetQueryHandler } from './nodes/node-children-get';
|
||||
import { NodeGetQueryHandler } from './nodes/node-get';
|
||||
import { NodeListQueryHandler } from './nodes/node-list';
|
||||
import { NodeReactionsListQueryHandler } from './nodes/node-reaction-list';
|
||||
import { NodeReactionsAggregateQueryHandler } from './nodes/node-reactions-aggregate';
|
||||
@@ -52,7 +50,6 @@ export const buildQueryHandlerMap = (app: AppService): QueryHandlerMap => {
|
||||
'account.list': new AccountListQueryHandler(app),
|
||||
'node.reaction.list': new NodeReactionsListQueryHandler(app),
|
||||
'node.reactions.aggregate': new NodeReactionsAggregateQueryHandler(app),
|
||||
'node.get': new NodeGetQueryHandler(app),
|
||||
'node.list': new NodeListQueryHandler(app),
|
||||
'node.tree.get': new NodeTreeGetQueryHandler(app),
|
||||
'record.list': new RecordListQueryHandler(app),
|
||||
@@ -72,7 +69,6 @@ export const buildQueryHandlerMap = (app: AppService): QueryHandlerMap => {
|
||||
'icon.list': new IconListQueryHandler(app),
|
||||
'icon.search': new IconSearchQueryHandler(app),
|
||||
'icon.category.list': new IconCategoryListQueryHandler(app),
|
||||
'node.children.get': new NodeChildrenGetQueryHandler(app),
|
||||
'radar.data.get': new RadarDataGetQueryHandler(app),
|
||||
'record.search': new RecordSearchQueryHandler(app),
|
||||
'local.file.get': new LocalFileGetQueryHandler(app),
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import { SelectNode } from '@colanode/client/databases/workspace';
|
||||
import { WorkspaceQueryHandlerBase } from '@colanode/client/handlers/queries/workspace-query-handler-base';
|
||||
import { mapNode } from '@colanode/client/lib';
|
||||
import { ChangeCheckResult, QueryHandler } from '@colanode/client/lib/types';
|
||||
import { NodeChildrenGetQueryInput } from '@colanode/client/queries/nodes/node-children-get';
|
||||
import { Event } from '@colanode/client/types/events';
|
||||
import { LocalNode } from '@colanode/client/types/nodes';
|
||||
|
||||
export class NodeChildrenGetQueryHandler
|
||||
extends WorkspaceQueryHandlerBase
|
||||
implements QueryHandler<NodeChildrenGetQueryInput>
|
||||
{
|
||||
public async handleQuery(
|
||||
input: NodeChildrenGetQueryInput
|
||||
): Promise<LocalNode[]> {
|
||||
const rows = await this.fetchChildren(input);
|
||||
return rows.map(mapNode) as LocalNode[];
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
event: Event,
|
||||
input: NodeChildrenGetQueryInput,
|
||||
output: LocalNode[]
|
||||
): Promise<ChangeCheckResult<NodeChildrenGetQueryInput>> {
|
||||
if (
|
||||
event.type === 'workspace.deleted' &&
|
||||
event.workspace.userId === input.userId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'node.created' &&
|
||||
event.workspace.userId === input.userId &&
|
||||
event.node.parentId === input.nodeId &&
|
||||
(input.types === undefined || input.types.includes(event.node.type))
|
||||
) {
|
||||
const newChildren = [...output, event.node];
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: newChildren,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'node.updated' &&
|
||||
event.workspace.userId === input.userId &&
|
||||
event.node.parentId === input.nodeId &&
|
||||
(input.types === undefined || input.types.includes(event.node.type))
|
||||
) {
|
||||
const node = output.find((n) => n.id === event.node.id);
|
||||
if (node) {
|
||||
const newChildren = output.map((node) =>
|
||||
node.id === event.node.id ? event.node : node
|
||||
);
|
||||
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: newChildren,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'node.deleted' &&
|
||||
event.workspace.userId === input.userId &&
|
||||
event.node.parentId === input.nodeId &&
|
||||
(input.types === undefined || input.types.includes(event.node.type))
|
||||
) {
|
||||
const node = output.find((n) => n.id === event.node.id);
|
||||
if (node) {
|
||||
const newChildren = output.filter((n) => n.id !== event.node.id);
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: newChildren,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchChildren(
|
||||
input: NodeChildrenGetQueryInput
|
||||
): Promise<SelectNode[]> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const rows = await workspace.database
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('parent_id', '=', input.nodeId)
|
||||
.where('type', 'in', input.types ?? [])
|
||||
.execute();
|
||||
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { SelectNode } from '@colanode/client/databases';
|
||||
import { WorkspaceQueryHandlerBase } from '@colanode/client/handlers/queries/workspace-query-handler-base';
|
||||
import { mapNode } from '@colanode/client/lib';
|
||||
import { ChangeCheckResult, QueryHandler } from '@colanode/client/lib/types';
|
||||
import { NodeGetQueryInput } from '@colanode/client/queries/nodes/node-get';
|
||||
import { Event } from '@colanode/client/types/events';
|
||||
import { LocalNode } from '@colanode/client/types/nodes';
|
||||
|
||||
export class NodeGetQueryHandler
|
||||
extends WorkspaceQueryHandlerBase
|
||||
implements QueryHandler<NodeGetQueryInput>
|
||||
{
|
||||
public async handleQuery(
|
||||
input: NodeGetQueryInput
|
||||
): Promise<LocalNode | null> {
|
||||
const row = await this.fetchNode(input);
|
||||
return row ? mapNode(row) : null;
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
event: Event,
|
||||
input: NodeGetQueryInput,
|
||||
_: LocalNode | null
|
||||
): Promise<ChangeCheckResult<NodeGetQueryInput>> {
|
||||
if (
|
||||
event.type === 'workspace.deleted' &&
|
||||
event.workspace.userId === input.userId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'node.created' &&
|
||||
event.workspace.userId === input.userId &&
|
||||
event.node.id === input.nodeId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: event.node,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'node.updated' &&
|
||||
event.workspace.userId === input.userId &&
|
||||
event.node.id === input.nodeId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: event.node,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'node.deleted' &&
|
||||
event.workspace.userId === input.userId &&
|
||||
event.node.id === input.nodeId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchNode(
|
||||
input: NodeGetQueryInput
|
||||
): Promise<SelectNode | undefined> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const row = await workspace.database
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('id', '=', input.nodeId)
|
||||
.executeTakeFirst();
|
||||
|
||||
return row;
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@ export * from './icons/icon-category-list';
|
||||
export * from './icons/icon-list';
|
||||
export * from './icons/icon-search';
|
||||
export * from './interactions/radar-data-get';
|
||||
export * from './nodes/node-children-get';
|
||||
export * from './nodes/node-get';
|
||||
export * from './nodes/node-reaction-list';
|
||||
export * from './nodes/node-reactions-aggregate';
|
||||
export * from './nodes/node-tree-get';
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { LocalNode } from '@colanode/client/types/nodes';
|
||||
import { NodeType } from '@colanode/core';
|
||||
|
||||
export type NodeChildrenGetQueryInput = {
|
||||
type: 'node.children.get';
|
||||
nodeId: string;
|
||||
userId: string;
|
||||
types?: NodeType[];
|
||||
};
|
||||
|
||||
declare module '@colanode/client/queries' {
|
||||
interface QueryMap {
|
||||
'node.children.get': {
|
||||
input: NodeChildrenGetQueryInput;
|
||||
output: LocalNode[];
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { LocalNode } from '@colanode/client/types/nodes';
|
||||
|
||||
export type NodeGetQueryInput = {
|
||||
type: 'node.get';
|
||||
nodeId: string;
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/queries' {
|
||||
interface QueryMap {
|
||||
'node.get': {
|
||||
input: NodeGetQueryInput;
|
||||
output: LocalNode | null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ import { Collection, createLiveQueryCollection, eq } from '@tanstack/react-db';
|
||||
|
||||
import {
|
||||
Download,
|
||||
LocalChannelNode,
|
||||
LocalChatNode,
|
||||
LocalDatabaseNode,
|
||||
LocalDatabaseViewNode,
|
||||
LocalFileNode,
|
||||
LocalFolderNode,
|
||||
LocalMessageNode,
|
||||
LocalNode,
|
||||
LocalPageNode,
|
||||
LocalRecordNode,
|
||||
LocalSpaceNode,
|
||||
Upload,
|
||||
@@ -35,9 +38,12 @@ class WorkspaceCollections {
|
||||
public readonly views: Collection<LocalDatabaseViewNode>;
|
||||
public readonly records: Collection<LocalRecordNode>;
|
||||
public readonly chats: Collection<LocalChatNode>;
|
||||
public readonly channels: Collection<LocalChannelNode>;
|
||||
public readonly spaces: Collection<LocalSpaceNode>;
|
||||
public readonly files: Collection<LocalFileNode>;
|
||||
public readonly messages: Collection<LocalMessageNode>;
|
||||
public readonly folders: Collection<LocalFolderNode>;
|
||||
public readonly pages: Collection<LocalPageNode>;
|
||||
|
||||
constructor(userId: string) {
|
||||
this.userId = userId;
|
||||
@@ -59,6 +65,11 @@ class WorkspaceCollections {
|
||||
this.chats = createLiveQueryCollection((q) =>
|
||||
q.from({ nodes: this.nodes }).where(({ nodes }) => eq(nodes.type, 'chat'))
|
||||
);
|
||||
this.channels = createLiveQueryCollection((q) =>
|
||||
q
|
||||
.from({ nodes: this.nodes })
|
||||
.where(({ nodes }) => eq(nodes.type, 'channel'))
|
||||
);
|
||||
this.spaces = createLiveQueryCollection((q) =>
|
||||
q
|
||||
.from({ nodes: this.nodes })
|
||||
@@ -77,6 +88,14 @@ class WorkspaceCollections {
|
||||
.from({ nodes: this.nodes })
|
||||
.where(({ nodes }) => eq(nodes.type, 'message'))
|
||||
);
|
||||
this.folders = createLiveQueryCollection((q) =>
|
||||
q
|
||||
.from({ nodes: this.nodes })
|
||||
.where(({ nodes }) => eq(nodes.type, 'folder'))
|
||||
);
|
||||
this.pages = createLiveQueryCollection((q) =>
|
||||
q.from({ nodes: this.nodes }).where(({ nodes }) => eq(nodes.type, 'page'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { LocalChannelNode } from '@colanode/client/types';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { UnreadBadge } from '@colanode/ui/components/ui/unread-badge';
|
||||
import { useRadar } from '@colanode/ui/contexts/radar';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface ChannelContainerTabProps {
|
||||
channelId: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export const ChannelContainerTab = ({
|
||||
channelId,
|
||||
isActive,
|
||||
}: ChannelContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const radar = useRadar();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: channelId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
const channel = nodeGetQuery.data as LocalChannelNode;
|
||||
if (!channel) {
|
||||
return <p className="text-sm text-muted-foreground">Not found</p>;
|
||||
}
|
||||
|
||||
const name =
|
||||
channel.attributes.name && channel.attributes.name.length > 0
|
||||
? channel.attributes.name
|
||||
: 'Unnamed';
|
||||
|
||||
const unreadState = radar.getNodeState(workspace.userId, channel.id);
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar
|
||||
size="small"
|
||||
id={channel.id}
|
||||
name={name}
|
||||
avatar={channel.attributes.avatar}
|
||||
/>
|
||||
<span>{name}</span>
|
||||
{!isActive && (
|
||||
<UnreadBadge
|
||||
count={unreadState.unreadCount}
|
||||
unread={unreadState.hasUnread}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
import { eq, useLiveQuery as useLiveQueryTanstack } from '@tanstack/react-db';
|
||||
|
||||
import { LocalChatNode } from '@colanode/client/types';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { UnreadBadge } from '@colanode/ui/components/ui/unread-badge';
|
||||
import { useRadar } from '@colanode/ui/contexts/radar';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface ChatContainerTabProps {
|
||||
chatId: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export const ChatContainerTab = ({
|
||||
chatId,
|
||||
isActive,
|
||||
}: ChatContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const radar = useRadar();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: chatId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
const chat = nodeGetQuery.data as LocalChatNode;
|
||||
const userId = chat
|
||||
? (Object.keys(chat.attributes.collaborators).find(
|
||||
(id) => id !== workspace.userId
|
||||
) ?? '')
|
||||
: '';
|
||||
|
||||
const userQuery = useLiveQueryTanstack((q) =>
|
||||
q
|
||||
.from({ users: collections.workspace(workspace.userId).users })
|
||||
.where(({ users }) => eq(users.id, userId))
|
||||
.select(({ users }) => ({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
avatar: users.avatar,
|
||||
}))
|
||||
.findOne()
|
||||
);
|
||||
const user = userQuery.data;
|
||||
|
||||
if (nodeGetQuery.isPending || userQuery.isLoading) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
if (!chat || !user) {
|
||||
return <p className="text-sm text-muted-foreground">Not found</p>;
|
||||
}
|
||||
|
||||
const unreadState = radar.getNodeState(workspace.userId, chat.id);
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar size="small" id={user.id} name={user.name} avatar={user.avatar} />
|
||||
<span>{user.name}</span>
|
||||
{!isActive && (
|
||||
<UnreadBadge
|
||||
count={unreadState.unreadCount}
|
||||
unread={unreadState.hasUnread}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import { LocalDatabaseNode } from '@colanode/client/types';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface DatabaseContainerTabProps {
|
||||
databaseId: string;
|
||||
}
|
||||
|
||||
export const DatabaseContainerTab = ({
|
||||
databaseId,
|
||||
}: DatabaseContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: databaseId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
const database = nodeGetQuery.data as LocalDatabaseNode;
|
||||
if (!database) {
|
||||
return <p className="text-sm text-muted-foreground">Not found</p>;
|
||||
}
|
||||
|
||||
const name =
|
||||
database.attributes.name && database.attributes.name.length > 0
|
||||
? database.attributes.name
|
||||
: 'Unnamed';
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar
|
||||
size="small"
|
||||
id={database.id}
|
||||
name={name}
|
||||
avatar={database.attributes.avatar}
|
||||
/>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { eq, inArray, useLiveQuery } from '@tanstack/react-db';
|
||||
import { ChevronDown, Trash2, X } from 'lucide-react';
|
||||
|
||||
import { LocalRecordNode } from '@colanode/client/types';
|
||||
@@ -5,6 +6,7 @@ import {
|
||||
DatabaseViewFieldFilterAttributes,
|
||||
RelationFieldAttributes,
|
||||
} from '@colanode/core';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
||||
import { RecordSearch } from '@colanode/ui/components/records/record-search';
|
||||
@@ -24,7 +26,6 @@ import {
|
||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQueries } from '@colanode/ui/hooks/use-live-queries';
|
||||
import { relationFieldFilterOperators } from '@colanode/ui/lib/databases';
|
||||
|
||||
interface ViewRelationFieldFilterProps {
|
||||
@@ -64,21 +65,22 @@ export const ViewRelationFieldFilter = ({
|
||||
) ?? relationFieldFilterOperators[0]!;
|
||||
|
||||
const relationIds = (filter.value as string[]) ?? [];
|
||||
const results = useLiveQueries(
|
||||
relationIds.map((id) => ({
|
||||
type: 'node.get',
|
||||
nodeId: id,
|
||||
userId: workspace.userId,
|
||||
}))
|
||||
const relationsQuery = useLiveQuery(
|
||||
(q) => {
|
||||
if (relationIds.length === 0 || !field.databaseId) {
|
||||
return q
|
||||
.from({ records: collections.workspace(workspace.userId).records })
|
||||
.where(({ records }) => eq(records.id, '')); // Return empty result
|
||||
}
|
||||
|
||||
return q
|
||||
.from({ records: collections.workspace(workspace.userId).records })
|
||||
.where(({ records }) => inArray(records.id, relationIds));
|
||||
},
|
||||
[workspace.userId, field.databaseId, relationIds]
|
||||
);
|
||||
|
||||
const relations: LocalRecordNode[] = [];
|
||||
for (const result of results) {
|
||||
if (result.data && result.data.type === 'record') {
|
||||
relations.push(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
const relations = relationsQuery.data;
|
||||
const hideInput = isOperatorWithoutValue(operator.value);
|
||||
|
||||
if (!field.databaseId) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
|
||||
import { LocalFileNode } from '@colanode/client/types';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { FileIcon } from '@colanode/ui/components/files/file-icon';
|
||||
import { FilePreview } from '@colanode/ui/components/files/file-preview';
|
||||
import { Link } from '@colanode/ui/components/ui/link';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
import { canPreviewFile } from '@colanode/ui/lib/files';
|
||||
|
||||
interface FileBlockProps {
|
||||
@@ -13,17 +15,20 @@ interface FileBlockProps {
|
||||
export const FileBlock = ({ id }: FileBlockProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
const fileGetQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ files: collections.workspace(workspace.userId).files })
|
||||
.where(({ files }) => eq(files.id, id))
|
||||
.findOne(),
|
||||
[workspace.userId, id]
|
||||
);
|
||||
|
||||
if (nodeGetQuery.isPending || !nodeGetQuery.data) {
|
||||
if (fileGetQuery.isLoading || !fileGetQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const file = nodeGetQuery.data as LocalFileNode;
|
||||
const file = fileGetQuery.data as LocalFileNode;
|
||||
const canPreview = canPreviewFile(file.attributes.subtype);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { LocalFileNode } from '@colanode/client/types';
|
||||
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface FileContainerTabProps {
|
||||
fileId: string;
|
||||
}
|
||||
|
||||
export const FileContainerTab = ({ fileId }: FileContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: fileId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
const file = nodeGetQuery.data as LocalFileNode;
|
||||
if (!file) {
|
||||
return <p className="text-sm text-muted-foreground">Not found</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<FileThumbnail
|
||||
userId={workspace.userId}
|
||||
file={file}
|
||||
className="size-4 rounded object-contain"
|
||||
/>
|
||||
<span>{file.attributes.name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
import { LocalFolderNode } from '@colanode/client/types';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface FolderContainerTabProps {
|
||||
folderId: string;
|
||||
}
|
||||
|
||||
export const FolderContainerTab = ({ folderId }: FolderContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: folderId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
const folder = nodeGetQuery.data as LocalFolderNode;
|
||||
if (!folder) {
|
||||
return <p className="text-sm text-muted-foreground">Not found</p>;
|
||||
}
|
||||
|
||||
const name =
|
||||
folder.attributes.name && folder.attributes.name.length > 0
|
||||
? folder.attributes.name
|
||||
: 'Unnamed';
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar
|
||||
size="small"
|
||||
id={folder.id}
|
||||
name={name}
|
||||
avatar={folder.attributes.avatar}
|
||||
/>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,11 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
|
||||
import { LocalMessageNode } from '@colanode/client/types';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { MessageAuthorAvatar } from '@colanode/ui/components/messages/message-author-avatar';
|
||||
import { MessageAuthorName } from '@colanode/ui/components/messages/message-author-name';
|
||||
import { MessageContent } from '@colanode/ui/components/messages/message-content';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface MessageReferenceProps {
|
||||
messageId: string;
|
||||
@@ -11,17 +13,20 @@ interface MessageReferenceProps {
|
||||
|
||||
export const MessageReference = ({ messageId }: MessageReferenceProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: messageId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
const messageGetQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ messages: collections.workspace(workspace.userId).messages })
|
||||
.where(({ messages }) => eq(messages.id, messageId))
|
||||
.findOne(),
|
||||
[workspace.userId, messageId]
|
||||
);
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
if (messageGetQuery.isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const message = nodeGetQuery.data as LocalMessageNode;
|
||||
const message = messageGetQuery.data as LocalMessageNode;
|
||||
|
||||
if (!message) {
|
||||
return (
|
||||
@@ -36,7 +41,7 @@ export const MessageReference = ({ messageId }: MessageReferenceProps) => {
|
||||
return (
|
||||
<div className="flex flex-row gap-2 border-l-4 p-2">
|
||||
<MessageAuthorAvatar message={message} className="size-5 mt-1" />
|
||||
<div className='"grow flex-col gap-1'>
|
||||
<div className="grow flex-col gap-1">
|
||||
<MessageAuthorName message={message} />
|
||||
<MessageContent message={message} />
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { ChannelTab } from '@colanode/ui/components/channels/channel-tab';
|
||||
import { ChatTab } from '@colanode/ui/components/chats/chat-tab';
|
||||
import { DatabaseTab } from '@colanode/ui/components/databases/database-tab';
|
||||
@@ -7,7 +10,6 @@ import { MessageTab } from '@colanode/ui/components/messages/message-tab';
|
||||
import { PageTab } from '@colanode/ui/components/pages/page-tab';
|
||||
import { RecordTab } from '@colanode/ui/components/records/record-tab';
|
||||
import { SpaceTab } from '@colanode/ui/components/spaces/space-tab';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface NodeTabProps {
|
||||
userId: string;
|
||||
@@ -15,13 +17,16 @@ interface NodeTabProps {
|
||||
}
|
||||
|
||||
export const NodeTab = ({ userId, nodeId }: NodeTabProps) => {
|
||||
const query = useLiveQuery({
|
||||
type: 'node.get',
|
||||
userId: userId,
|
||||
nodeId: nodeId,
|
||||
});
|
||||
const query = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ nodes: collections.workspace(userId).nodes })
|
||||
.where(({ nodes }) => eq(nodes.id, nodeId))
|
||||
.findOne(),
|
||||
[userId, nodeId]
|
||||
);
|
||||
|
||||
if (query.isPending) {
|
||||
if (query.isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { LocalPageNode } from '@colanode/client/types';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface PageContainerTabProps {
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
export const PageContainerTab = ({ pageId }: PageContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: pageId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
const page = nodeGetQuery.data as LocalPageNode;
|
||||
if (!page) {
|
||||
return <p className="text-sm text-muted-foreground">Not found</p>;
|
||||
}
|
||||
|
||||
const name =
|
||||
page.attributes.name && page.attributes.name.length > 0
|
||||
? page.attributes.name
|
||||
: 'Unnamed';
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar
|
||||
size="small"
|
||||
id={page.id}
|
||||
name={name}
|
||||
avatar={page.attributes.avatar}
|
||||
/>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
import { LocalRecordNode } from '@colanode/client/types';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface RecordContainerTabProps {
|
||||
recordId: string;
|
||||
}
|
||||
|
||||
export const RecordContainerTab = ({ recordId }: RecordContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: recordId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
const record = nodeGetQuery.data as LocalRecordNode;
|
||||
if (!record) {
|
||||
return <p>Not found</p>;
|
||||
}
|
||||
|
||||
const name =
|
||||
record.attributes.name && record.attributes.name.length > 0
|
||||
? record.attributes.name
|
||||
: 'Unnamed';
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar
|
||||
size="small"
|
||||
id={record.id}
|
||||
name={name}
|
||||
avatar={record.attributes.avatar}
|
||||
/>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
|
||||
import { LocalDatabaseNode } from '@colanode/client/types';
|
||||
import { NodeRole } from '@colanode/core';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Database } from '@colanode/ui/components/databases/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface RecordDatabaseProps {
|
||||
id: string;
|
||||
@@ -13,21 +15,24 @@ interface RecordDatabaseProps {
|
||||
export const RecordDatabase = ({ id, role, children }: RecordDatabaseProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
userId: workspace.userId,
|
||||
nodeId: id,
|
||||
});
|
||||
const databaseGetQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ databases: collections.workspace(workspace.userId).databases })
|
||||
.where(({ databases }) => eq(databases.id, id))
|
||||
.findOne(),
|
||||
[workspace.userId, id]
|
||||
);
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
if (databaseGetQuery.isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nodeGetQuery.data) {
|
||||
if (!databaseGetQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const database = nodeGetQuery.data as LocalDatabaseNode;
|
||||
const database = databaseGetQuery.data as LocalDatabaseNode;
|
||||
return (
|
||||
<Database database={database} role={role}>
|
||||
{children}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { eq, inArray, useLiveQuery } from '@tanstack/react-db';
|
||||
import { X } from 'lucide-react';
|
||||
import { Fragment, useState } from 'react';
|
||||
|
||||
import { LocalRecordNode } from '@colanode/client/types';
|
||||
import { RelationFieldAttributes } from '@colanode/core';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { RecordSearch } from '@colanode/ui/components/records/record-search';
|
||||
import { Badge } from '@colanode/ui/components/ui/badge';
|
||||
@@ -14,7 +16,6 @@ import {
|
||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||
import { useRecord } from '@colanode/ui/contexts/record';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQueries } from '@colanode/ui/hooks/use-live-queries';
|
||||
|
||||
interface RecordRelationValueProps {
|
||||
field: RelationFieldAttributes;
|
||||
@@ -46,21 +47,22 @@ export const RecordRelationValue = ({
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const relationIds = record.getRelationValue(field) ?? [];
|
||||
const results = useLiveQueries(
|
||||
relationIds.map((id) => ({
|
||||
type: 'node.get',
|
||||
nodeId: id,
|
||||
userId: workspace.userId,
|
||||
}))
|
||||
const relationsQuery = useLiveQuery(
|
||||
(q) => {
|
||||
if (relationIds.length === 0 || !field.databaseId) {
|
||||
return q
|
||||
.from({ records: collections.workspace(workspace.userId).records })
|
||||
.where(({ records }) => eq(records.id, '')); // Return empty result
|
||||
}
|
||||
|
||||
return q
|
||||
.from({ records: collections.workspace(workspace.userId).records })
|
||||
.where(({ records }) => inArray(records.id, relationIds));
|
||||
},
|
||||
[workspace.userId, field.databaseId, relationIds]
|
||||
);
|
||||
|
||||
const relations: LocalRecordNode[] = [];
|
||||
for (const result of results) {
|
||||
if (result.data && result.data.type === 'record') {
|
||||
relations.push(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
const relations = relationsQuery.data;
|
||||
if (!field.databaseId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { LocalSpaceNode } from '@colanode/client/types';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface SpaceContainerTabProps {
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export const SpaceContainerTab = ({ spaceId }: SpaceContainerTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: spaceId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
return <p className="text-sm text-muted-foreground">Loading...</p>;
|
||||
}
|
||||
|
||||
const space = nodeGetQuery.data as LocalSpaceNode;
|
||||
if (!space) {
|
||||
return <p className="text-sm text-muted-foreground">Not found</p>;
|
||||
}
|
||||
|
||||
const name =
|
||||
space.attributes.name && space.attributes.name.length > 0
|
||||
? space.attributes.name
|
||||
: 'Unnamed';
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar
|
||||
size="small"
|
||||
id={space.id}
|
||||
name={name}
|
||||
avatar={space.attributes.avatar}
|
||||
/>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { RefAttributes, useRef } from 'react';
|
||||
import { useDrop } from 'react-dnd';
|
||||
@@ -5,6 +6,7 @@ import { toast } from 'sonner';
|
||||
|
||||
import { LocalSpaceNode } from '@colanode/client/types';
|
||||
import { extractNodeRole } from '@colanode/core';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { WorkspaceSidebarItem } from '@colanode/ui/components/layouts/sidebars/sidebar-item';
|
||||
import { SpaceSidebarDropdown } from '@colanode/ui/components/spaces/space-sidebar-dropdown';
|
||||
@@ -15,7 +17,6 @@ import {
|
||||
} from '@colanode/ui/components/ui/collapsible';
|
||||
import { Link } from '@colanode/ui/components/ui/link';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { sortSpaceChildren } from '@colanode/ui/lib/spaces';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
@@ -31,12 +32,13 @@ export const SpaceSidebarItem = ({ space }: SpaceSidebarItemProps) => {
|
||||
const role = extractNodeRole(space, workspace.userId);
|
||||
const canEdit = role === 'admin';
|
||||
|
||||
const nodeChildrenGetQuery = useLiveQuery({
|
||||
type: 'node.children.get',
|
||||
nodeId: space.id,
|
||||
userId: workspace.userId,
|
||||
types: ['page', 'channel', 'database', 'folder'],
|
||||
});
|
||||
const nodeChildrenGetQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ nodes: collections.workspace(workspace.userId).nodes })
|
||||
.where(({ nodes }) => eq(nodes.parentId, space.id)),
|
||||
[workspace.userId, space.id]
|
||||
);
|
||||
|
||||
const [dropMonitor, dropRef] = useDrop({
|
||||
accept: 'sidebar-item',
|
||||
@@ -52,7 +54,7 @@ export const SpaceSidebarItem = ({ space }: SpaceSidebarItemProps) => {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const dropDivRef = dropRef(divRef);
|
||||
|
||||
const children = sortSpaceChildren(space, nodeChildrenGetQuery.data ?? []);
|
||||
const children = sortSpaceChildren(space, nodeChildrenGetQuery.data);
|
||||
|
||||
const handleDragEnd = (childId: string, after: string | null) => {
|
||||
mutation.mutate({
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { Folder } from 'lucide-react';
|
||||
|
||||
import { LocalFileNode, Download } from '@colanode/client/types';
|
||||
import { formatBytes, timeAgo } from '@colanode/core';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { FileIcon } from '@colanode/ui/components/files/file-icon';
|
||||
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
|
||||
import {
|
||||
@@ -12,7 +14,6 @@ import {
|
||||
} from '@colanode/ui/components/ui/tooltip';
|
||||
import { WorkspaceDownloadStatus } from '@colanode/ui/components/workspaces/downloads/workspace-download-status';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface WorkspaceDownloadFileProps {
|
||||
download: Download;
|
||||
@@ -24,11 +25,14 @@ export const WorkspaceDownloadFile = ({
|
||||
const workspace = useWorkspace();
|
||||
const navigate = useNavigate({ from: '/workspace/$userId' });
|
||||
|
||||
const fileQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
userId: workspace.userId,
|
||||
nodeId: download.fileId,
|
||||
});
|
||||
const fileQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ files: collections.workspace(workspace.userId).files })
|
||||
.where(({ files }) => eq(files.id, download.fileId))
|
||||
.findOne(),
|
||||
[workspace.userId, download.fileId]
|
||||
);
|
||||
|
||||
const file = fileQuery.data as LocalFileNode | undefined;
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
import { BadgeAlert } from 'lucide-react';
|
||||
|
||||
import { Upload, LocalFileNode } from '@colanode/client/types';
|
||||
import { formatBytes, timeAgo } from '@colanode/core';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
|
||||
import { Link } from '@colanode/ui/components/ui/link';
|
||||
import { WorkspaceUploadStatus } from '@colanode/ui/components/workspaces/uploads/workspace-upload-status';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface WorkspaceUploadFileProps {
|
||||
upload: Upload;
|
||||
@@ -15,11 +16,14 @@ interface WorkspaceUploadFileProps {
|
||||
export const WorkspaceUploadFile = ({ upload }: WorkspaceUploadFileProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const fileQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
userId: workspace.userId,
|
||||
nodeId: upload.fileId,
|
||||
});
|
||||
const fileQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ files: collections.workspace(workspace.userId).files })
|
||||
.where(({ files }) => eq(files.id, upload.fileId))
|
||||
.findOne(),
|
||||
[workspace.userId, upload.fileId]
|
||||
);
|
||||
|
||||
const file = fileQuery.data as LocalFileNode;
|
||||
|
||||
|
||||
@@ -1,37 +1,46 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
import { type NodeViewProps } from '@tiptap/core';
|
||||
import { NodeViewWrapper } from '@tiptap/react';
|
||||
|
||||
import { LocalFolderNode } from '@colanode/client/types';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { Link } from '@colanode/ui/components/ui/link';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
export const FolderNodeView = ({ node }: NodeViewProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const id = node.attrs.id;
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
const folderGetQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ folders: collections.workspace(workspace.userId).folders })
|
||||
.where(({ folders }) => eq(folders.id, id))
|
||||
.select(({ folders }) => ({
|
||||
id: folders.id,
|
||||
name: folders.attributes.name,
|
||||
avatar: folders.attributes.avatar,
|
||||
}))
|
||||
.findOne(),
|
||||
[workspace.userId, id]
|
||||
);
|
||||
|
||||
if (folderGetQuery.isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const folder = nodeGetQuery.data as LocalFolderNode;
|
||||
const folder = folderGetQuery.data;
|
||||
if (!folder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = folder.attributes.name ?? 'Unnamed';
|
||||
const avatar = folder.attributes.avatar;
|
||||
const name = folder.name ?? 'Unnamed';
|
||||
const avatar = folder.avatar;
|
||||
|
||||
return (
|
||||
<NodeViewWrapper data-id={node.attrs.id}>
|
||||
|
||||
@@ -1,37 +1,46 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
import { type NodeViewProps } from '@tiptap/core';
|
||||
import { NodeViewWrapper } from '@tiptap/react';
|
||||
|
||||
import { LocalPageNode } from '@colanode/client/types';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { Link } from '@colanode/ui/components/ui/link';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
export const PageNodeView = ({ node }: NodeViewProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const id = node.attrs.id;
|
||||
const nodeGetQuery = useLiveQuery({
|
||||
type: 'node.get',
|
||||
nodeId: id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (nodeGetQuery.isPending) {
|
||||
const pageGetQuery = useLiveQuery(
|
||||
(q) =>
|
||||
q
|
||||
.from({ pages: collections.workspace(workspace.userId).pages })
|
||||
.where(({ pages }) => eq(pages.id, id))
|
||||
.select(({ pages }) => ({
|
||||
id: pages.id,
|
||||
name: pages.attributes.name,
|
||||
avatar: pages.attributes.avatar,
|
||||
}))
|
||||
.findOne(),
|
||||
[workspace.userId, id]
|
||||
);
|
||||
|
||||
if (pageGetQuery.isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const page = nodeGetQuery.data as LocalPageNode;
|
||||
const page = pageGetQuery.data;
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = page.attributes.name ?? 'Unnamed';
|
||||
const avatar = page.attributes.avatar;
|
||||
const name = page.name ?? 'Unnamed';
|
||||
const avatar = page.avatar;
|
||||
|
||||
return (
|
||||
<NodeViewWrapper data-id={node.attrs.id}>
|
||||
|
||||
Reference in New Issue
Block a user