Use tanstackdb for folder creation

This commit is contained in:
Hakan Shehu
2025-11-20 18:16:49 -08:00
parent 8b233ca046
commit a1dab1cb27
8 changed files with 87 additions and 127 deletions

View File

@@ -1,36 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
FolderCreateMutationInput,
FolderCreateMutationOutput,
} from '@colanode/client/mutations';
import { FolderAttributes, generateId, IdType } from '@colanode/core';
export class FolderCreateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<FolderCreateMutationInput>
{
async handleMutation(
input: FolderCreateMutationInput
): Promise<FolderCreateMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const id = generateId(IdType.Folder);
const attributes: FolderAttributes = {
type: 'folder',
parentId: input.parentId,
name: input.name,
avatar: input.avatar,
};
await workspace.nodes.createNode({
id,
attributes,
parentId: input.parentId,
});
return {
id: id,
};
}
}

View File

@@ -35,7 +35,6 @@ import { DocumentUpdateMutationHandler } from './documents/document-update';
import { FileCreateMutationHandler } from './files/file-create';
import { FileDownloadMutationHandler } from './files/file-download';
import { TempFileCreateMutationHandler } from './files/temp-file-create';
import { FolderCreateMutationHandler } from './folders/folder-create';
import { FolderUpdateMutationHandler } from './folders/folder-update';
import { MessageCreateMutationHandler } from './messages/message-create';
import { NodeCollaboratorCreateMutationHandler } from './nodes/node-collaborator-create';
@@ -117,7 +116,6 @@ export const buildMutationHandlerMap = (
'workspace.update': new WorkspaceUpdateMutationHandler(app),
'avatar.upload': new AvatarUploadMutationHandler(app),
'account.logout': new AccountLogoutMutationHandler(app),
'folder.create': new FolderCreateMutationHandler(app),
'file.create': new FileCreateMutationHandler(app),
'file.download': new FileDownloadMutationHandler(app),
'space.update': new SpaceUpdateMutationHandler(app),

View File

@@ -1,21 +0,0 @@
export type FolderCreateMutationInput = {
type: 'folder.create';
userId: string;
parentId: string;
name: string;
avatar?: string | null;
generateIndex: boolean;
};
export type FolderCreateMutationOutput = {
id: string;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'folder.create': {
input: FolderCreateMutationInput;
output: FolderCreateMutationOutput;
};
}
}

View File

@@ -27,7 +27,6 @@ export * from './databases/database-name-field-update';
export * from './documents/document-update';
export * from './files/file-create';
export * from './files/file-download';
export * from './folders/folder-create';
export * from './folders/folder-update';
export * from './messages/message-create';
export * from './nodes/node-collaborator-create';

View File

@@ -1,8 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import { LocalFolderNode } from '@colanode/client/types';
import { generateId, IdType } from '@colanode/core';
import { FolderForm } from '@colanode/ui/components/folders/folder-form';
import { collections } from '@colanode/ui/collections';
import {
FolderForm,
FolderFormValues,
} from '@colanode/ui/components/folders/folder-form';
import {
Dialog,
DialogContent,
@@ -11,7 +17,6 @@ import {
DialogTitle,
} from '@colanode/ui/components/ui/dialog';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface FolderCreateDialogProps {
spaceId: string;
@@ -26,7 +31,45 @@ export const FolderCreateDialog = ({
}: FolderCreateDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const { mutate, isPending } = useMutation();
const { mutate } = useMutation({
mutationFn: async (values: FolderFormValues) => {
const folderId = generateId(IdType.Folder);
const nodes = collections.workspace(workspace.userId).nodes;
const folder: LocalFolderNode = {
id: folderId,
type: 'folder',
attributes: {
type: 'folder',
name: values.name,
parentId: spaceId,
},
parentId: spaceId,
rootId: spaceId,
createdAt: new Date().toISOString(),
createdBy: workspace.userId,
updatedAt: null,
updatedBy: null,
localRevision: '0',
serverRevision: '0',
};
nodes.insert(folder);
return folder;
},
onSuccess: (folder) => {
navigate({
to: '$nodeId',
params: {
nodeId: folder.id,
},
});
onOpenChange(false);
},
onError: (error) => {
toast.error(error.message);
},
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -42,36 +85,12 @@ export const FolderCreateDialog = ({
values={{
name: '',
}}
isPending={isPending}
submitText="Create"
handleCancel={() => {
onCancel={() => {
onOpenChange(false);
}}
handleSubmit={(values) => {
if (isPending) {
return;
}
mutate({
input: {
type: 'folder.create',
parentId: spaceId,
name: values.name,
avatar: values.avatar,
userId: workspace.userId,
generateIndex: true,
},
onSuccess(output) {
onOpenChange(false);
navigate({
to: '$nodeId',
params: { nodeId: output.id },
});
},
onError(error) {
toast.error(error.message);
},
});
onSubmit={(values) => {
mutate(values);
}}
/>
</DialogContent>

View File

@@ -14,30 +14,29 @@ import {
FormMessage,
} from '@colanode/ui/components/ui/form';
import { Input } from '@colanode/ui/components/ui/input';
import { Spinner } from '@colanode/ui/components/ui/spinner';
const formSchema = z.object({
name: z.string().min(3, 'Name must be at least 3 characters long.'),
avatar: z.string().optional().nullable(),
});
export type FolderFormValues = z.infer<typeof formSchema>;
interface FolderFormProps {
id: string;
values: z.infer<typeof formSchema>;
isPending: boolean;
submitText: string;
handleCancel: () => void;
handleSubmit: (values: z.infer<typeof formSchema>) => void;
onCancel: () => void;
onSubmit: (values: z.infer<typeof formSchema>) => void;
readOnly?: boolean;
}
export const FolderForm = ({
id,
values,
isPending,
submitText,
handleCancel,
handleSubmit,
onCancel,
onSubmit,
readOnly = false,
}: FolderFormProps) => {
const form = useForm<z.infer<typeof formSchema>>({
@@ -60,10 +59,7 @@ export const FolderForm = ({
return (
<Form {...form}>
<form
className="flex flex-col"
onSubmit={form.handleSubmit(handleSubmit)}
>
<form className="flex flex-col" onSubmit={form.handleSubmit(onSubmit)}>
<div className="grow flex flex-row items-end gap-2 py-2 pb-4">
{readOnly ? (
<Button type="button" variant="outline" size="icon">
@@ -72,7 +68,6 @@ export const FolderForm = ({
) : (
<AvatarPopover
onPick={(avatar) => {
if (isPending) return;
if (avatar === values.avatar) return;
form.setValue('avatar', avatar);
@@ -102,16 +97,10 @@ export const FolderForm = ({
/>
</div>
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
disabled={isPending}
onClick={handleCancel}
>
<Button type="button" variant="outline" onClick={onCancel}>
Cancel
</Button>
<Button type="submit" disabled={isPending || readOnly}>
{isPending && <Spinner className="mr-1" />}
<Button type="submit" disabled={readOnly}>
{submitText}
</Button>
</div>

View File

@@ -43,13 +43,12 @@ export const FolderUpdateDialog = ({
name: folder.attributes.name,
avatar: folder.attributes.avatar,
}}
isPending={isPending}
submitText="Update"
readOnly={!canEdit}
handleCancel={() => {
onCancel={() => {
onOpenChange(false);
}}
handleSubmit={(values) => {
onSubmit={(values) => {
if (isPending) {
return;
}

View File

@@ -1,6 +1,8 @@
import { Folder } from 'lucide-react';
import { EditorCommand } from '@colanode/client/types';
import { EditorCommand, LocalFolderNode } from '@colanode/client/types';
import { generateId, IdType } from '@colanode/core/lib/id.js';
import { collections } from '@colanode/ui/collections';
export const FolderCommand: EditorCommand = {
key: 'folder',
@@ -14,19 +16,30 @@ export const FolderCommand: EditorCommand = {
return;
}
const { userId, documentId } = context;
const output = await window.colanode.executeMutation({
type: 'folder.create',
name: 'Untitled',
avatar: null,
userId,
parentId: documentId,
generateIndex: false,
});
const { userId, documentId, rootId } = context;
const folderId = generateId(IdType.Folder);
const nodes = collections.workspace(userId).nodes;
if (!output.success) {
return;
}
const folder: LocalFolderNode = {
id: folderId,
type: 'folder',
attributes: {
type: 'folder',
name: 'Untitled',
avatar: null,
parentId: documentId,
},
parentId: documentId,
rootId: rootId,
createdAt: new Date().toISOString(),
createdBy: userId,
updatedAt: null,
updatedBy: null,
localRevision: '0',
serverRevision: '0',
};
nodes.insert(folder);
editor
.chain()
@@ -35,7 +48,7 @@ export const FolderCommand: EditorCommand = {
.insertContent({
type: 'folder',
attrs: {
id: output.output.id,
id: folder.id,
},
})
.run();