mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
Use tanstackdb for view create & delete
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
||||
import { mapNode } from '@colanode/client/lib/mappers';
|
||||
import { MutationHandler } from '@colanode/client/lib/types';
|
||||
import {
|
||||
ViewCreateMutationInput,
|
||||
ViewCreateMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/view-create';
|
||||
import {
|
||||
generateId,
|
||||
generateFractionalIndex,
|
||||
IdType,
|
||||
DatabaseViewAttributes,
|
||||
} from '@colanode/core';
|
||||
|
||||
export class ViewCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewCreateMutationInput
|
||||
): Promise<ViewCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const id = generateId(IdType.DatabaseView);
|
||||
const otherViews = await workspace.database
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('parent_id', '=', input.databaseId)
|
||||
.where('type', '=', 'database_view')
|
||||
.execute();
|
||||
|
||||
let maxIndex: string | null = null;
|
||||
for (const otherView of otherViews) {
|
||||
const view = mapNode(otherView);
|
||||
if (view.type !== 'database_view') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const index = view.index;
|
||||
if (maxIndex === null || index > maxIndex) {
|
||||
maxIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
const attributes: DatabaseViewAttributes = {
|
||||
type: 'database_view',
|
||||
name: input.name,
|
||||
index: generateFractionalIndex(maxIndex, null),
|
||||
layout: input.viewType,
|
||||
parentId: input.databaseId,
|
||||
};
|
||||
|
||||
await workspace.nodes.createNode({
|
||||
id: id,
|
||||
attributes: attributes,
|
||||
parentId: input.databaseId,
|
||||
});
|
||||
|
||||
return {
|
||||
id: id,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
||||
import { MutationHandler } from '@colanode/client/lib/types';
|
||||
import {
|
||||
ViewDeleteMutationInput,
|
||||
ViewDeleteMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/view-delete';
|
||||
|
||||
export class ViewDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewDeleteMutationInput
|
||||
): Promise<ViewDeleteMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
await workspace.nodes.deleteNode(input.viewId);
|
||||
|
||||
return {
|
||||
id: input.viewId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
||||
import { MutationHandler } from '@colanode/client/lib/types';
|
||||
import { MutationErrorCode, MutationError } from '@colanode/client/mutations';
|
||||
import {
|
||||
ViewNameUpdateMutationInput,
|
||||
ViewNameUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/view-name-update';
|
||||
import { DatabaseViewAttributes } from '@colanode/core';
|
||||
|
||||
export class ViewNameUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewNameUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewNameUpdateMutationInput
|
||||
): Promise<ViewNameUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<DatabaseViewAttributes>(
|
||||
input.viewId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.ViewUpdateForbidden,
|
||||
"You don't have permission to update this view."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.ViewUpdateFailed,
|
||||
'Something went wrong while updating the view.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: input.viewId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@ import { FieldNameUpdateMutationHandler } from './databases/field-name-update';
|
||||
import { SelectOptionCreateMutationHandler } from './databases/select-option-create';
|
||||
import { SelectOptionDeleteMutationHandler } from './databases/select-option-delete';
|
||||
import { SelectOptionUpdateMutationHandler } from './databases/select-option-update';
|
||||
import { ViewCreateMutationHandler } from './databases/view-create';
|
||||
import { ViewDeleteMutationHandler } from './databases/view-delete';
|
||||
import { ViewNameUpdateMutationHandler } from './databases/view-name-update';
|
||||
import { ViewUpdateMutationHandler } from './databases/view-update';
|
||||
import { DocumentUpdateMutationHandler } from './documents/document-update';
|
||||
import { FileCreateMutationHandler } from './files/file-create';
|
||||
@@ -66,7 +63,6 @@ export const buildMutationHandlerMap = (
|
||||
'email.register': new EmailRegisterMutationHandler(app),
|
||||
'email.verify': new EmailVerifyMutationHandler(app),
|
||||
'google.login': new GoogleLoginMutationHandler(app),
|
||||
'view.create': new ViewCreateMutationHandler(app),
|
||||
'node.delete': new NodeDeleteMutationHandler(app),
|
||||
'node.create': new NodeCreateMutationHandler(app),
|
||||
'node.update': new NodeUpdateMutationHandler(app),
|
||||
@@ -102,8 +98,6 @@ export const buildMutationHandlerMap = (
|
||||
'space.child.reorder': new SpaceChildReorderMutationHandler(app),
|
||||
'account.update': new AccountUpdateMutationHandler(app),
|
||||
'view.update': new ViewUpdateMutationHandler(app),
|
||||
'view.delete': new ViewDeleteMutationHandler(app),
|
||||
'view.name.update': new ViewNameUpdateMutationHandler(app),
|
||||
'document.update': new DocumentUpdateMutationHandler(app),
|
||||
'metadata.update': new MetadataUpdateMutationHandler(app),
|
||||
'metadata.delete': new MetadataDeleteMutationHandler(app),
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
export type ViewCreateMutationInput = {
|
||||
type: 'view.create';
|
||||
userId: string;
|
||||
databaseId: string;
|
||||
viewType: 'table' | 'board' | 'calendar';
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ViewCreateMutationOutput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'view.create': {
|
||||
input: ViewCreateMutationInput;
|
||||
output: ViewCreateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export type ViewDeleteMutationInput = {
|
||||
type: 'view.delete';
|
||||
userId: string;
|
||||
viewId: string;
|
||||
};
|
||||
|
||||
export type ViewDeleteMutationOutput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'view.delete': {
|
||||
input: ViewDeleteMutationInput;
|
||||
output: ViewDeleteMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export type ViewNameUpdateMutationInput = {
|
||||
type: 'view.name.update';
|
||||
userId: string;
|
||||
databaseId: string;
|
||||
viewId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ViewNameUpdateMutationOutput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'view.name.update': {
|
||||
input: ViewNameUpdateMutationInput;
|
||||
output: ViewNameUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@ export * from './databases/field-name-update';
|
||||
export * from './databases/select-option-create';
|
||||
export * from './databases/select-option-delete';
|
||||
export * from './databases/select-option-update';
|
||||
export * from './databases/view-create';
|
||||
export * from './databases/view-delete';
|
||||
export * from './databases/view-name-update';
|
||||
export * from './databases/view-update';
|
||||
export * from './databases/database-name-field-update';
|
||||
export * from './documents/document-update';
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Fragment, useState } from 'react';
|
||||
import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
|
||||
import { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
|
||||
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
||||
import { ViewDeleteDialog } from '@colanode/ui/components/databases/view-delete-dialog';
|
||||
import { ViewIcon } from '@colanode/ui/components/databases/view-icon';
|
||||
import { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
|
||||
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
@@ -179,7 +179,9 @@ export const BoardViewSettings = () => {
|
||||
/>
|
||||
)}
|
||||
{openDelete && (
|
||||
<ViewDeleteDialog
|
||||
<NodeDeleteDialog
|
||||
title="Are you sure you want delete this view?"
|
||||
description="This action cannot be undone. This view will no longer be accessible and all data in the view will be lost."
|
||||
id={view.id}
|
||||
open={openDelete}
|
||||
onOpenChange={setOpenDelete}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Fragment, useState } from 'react';
|
||||
import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
|
||||
import { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
|
||||
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
||||
import { ViewDeleteDialog } from '@colanode/ui/components/databases/view-delete-dialog';
|
||||
import { ViewIcon } from '@colanode/ui/components/databases/view-icon';
|
||||
import { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
|
||||
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
@@ -179,7 +179,9 @@ export const CalendarViewSettings = () => {
|
||||
/>
|
||||
)}
|
||||
{openDelete && (
|
||||
<ViewDeleteDialog
|
||||
<NodeDeleteDialog
|
||||
title="Are you sure you want delete this view?"
|
||||
description="This action cannot be undone. This view will no longer be accessible and all data in the view will be lost."
|
||||
id={view.id}
|
||||
open={openDelete}
|
||||
onOpenChange={setOpenDelete}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Fragment, useState } from 'react';
|
||||
import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
|
||||
import { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
|
||||
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
||||
import { ViewDeleteDialog } from '@colanode/ui/components/databases/view-delete-dialog';
|
||||
import { ViewIcon } from '@colanode/ui/components/databases/view-icon';
|
||||
import { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
|
||||
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
@@ -179,7 +179,9 @@ export const TableViewSettings = () => {
|
||||
/>
|
||||
)}
|
||||
{openDelete && (
|
||||
<ViewDeleteDialog
|
||||
<NodeDeleteDialog
|
||||
title="Are you sure you want delete this view?"
|
||||
description="This action cannot be undone. This view will no longer be accessible and all data in the view will be lost."
|
||||
id={view.id}
|
||||
open={openDelete}
|
||||
onOpenChange={setOpenDelete}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { Calendar, Columns, Table } from 'lucide-react';
|
||||
import { FC } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import { LocalDatabaseViewNode } from '@colanode/client/types';
|
||||
import {
|
||||
compareString,
|
||||
generateFractionalIndex,
|
||||
generateId,
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
import { collections } from '@colanode/ui/collections';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -26,7 +35,6 @@ import { Input } from '@colanode/ui/components/ui/input';
|
||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -34,6 +42,8 @@ const formSchema = z.object({
|
||||
type: z.enum(['table', 'board', 'calendar']),
|
||||
});
|
||||
|
||||
type ViewCreateFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
interface ViewTypeOption {
|
||||
name: string;
|
||||
icon: FC;
|
||||
@@ -69,7 +79,6 @@ export const ViewCreateDialog = ({
|
||||
}: ViewCreateDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -79,44 +88,62 @@ export const ViewCreateDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: async (values: ViewCreateFormValues) => {
|
||||
const type = viewTypes.find((viewType) => viewType.type === values.type);
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = values.name;
|
||||
if (name === '') {
|
||||
name = type.name;
|
||||
}
|
||||
|
||||
const nodes = collections.workspace(workspace.userId).nodes;
|
||||
let maxIndex: string | null = null;
|
||||
nodes.forEach((node) => {
|
||||
if (node.type === 'database_view' && node.parentId === database.id) {
|
||||
const index = node.index;
|
||||
if (maxIndex === null || compareString(index, maxIndex) > 0) {
|
||||
maxIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const viewId = generateId(IdType.DatabaseView);
|
||||
const view: LocalDatabaseViewNode = {
|
||||
id: viewId,
|
||||
type: 'database_view',
|
||||
name: name,
|
||||
parentId: database.id,
|
||||
layout: type.type,
|
||||
index: generateFractionalIndex(maxIndex, null),
|
||||
rootId: database.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdBy: workspace.userId,
|
||||
updatedAt: null,
|
||||
updatedBy: null,
|
||||
localRevision: '0',
|
||||
serverRevision: '0',
|
||||
};
|
||||
nodes.insert(view);
|
||||
return viewId;
|
||||
},
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
onOpenChange(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
form.reset();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
const type = viewTypes.find((viewType) => viewType.type === values.type);
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = values.name;
|
||||
if (name === '') {
|
||||
name = type.name;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'view.create',
|
||||
viewType: type.type,
|
||||
databaseId: database.id,
|
||||
name: name,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess() {
|
||||
form.reset();
|
||||
onOpenChange(false);
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (!database.canEdit) {
|
||||
return null;
|
||||
}
|
||||
@@ -133,7 +160,7 @@ export const ViewCreateDialog = ({
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex flex-col"
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
onSubmit={form.handleSubmit((values) => mutate(values))}
|
||||
>
|
||||
<div className="grow space-y-4 py-2 pb-4">
|
||||
<FormField
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@colanode/ui/components/ui/alert-dialog';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
|
||||
interface ViewDeleteDialogProps {
|
||||
id: string;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const ViewDeleteDialog = ({
|
||||
id,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: ViewDeleteDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
if (!database.canEdit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
Are you sure you want delete this view?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This view will no longer be accessible
|
||||
and all data in the view will be lost.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={isPending}
|
||||
onClick={() => {
|
||||
mutate({
|
||||
input: {
|
||||
type: 'view.delete',
|
||||
viewId: id,
|
||||
databaseId: database.id,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess() {
|
||||
onOpenChange(false);
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isPending && <Spinner className="mr-1" />}
|
||||
Delete
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user