mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 19:57:46 +01:00
Use tanstackdb for some field mutations
This commit is contained in:
@@ -1,47 +0,0 @@
|
|||||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
|
||||||
import { MutationHandler } from '@colanode/client/lib/types';
|
|
||||||
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
|
|
||||||
import {
|
|
||||||
DatabaseNameFieldUpdateMutationInput,
|
|
||||||
DatabaseNameFieldUpdateMutationOutput,
|
|
||||||
} from '@colanode/client/mutations/databases/database-name-field-update';
|
|
||||||
import { DatabaseAttributes } from '@colanode/core';
|
|
||||||
|
|
||||||
export class DatabaseNameFieldUpdateMutationHandler
|
|
||||||
extends WorkspaceMutationHandlerBase
|
|
||||||
implements MutationHandler<DatabaseNameFieldUpdateMutationInput>
|
|
||||||
{
|
|
||||||
async handleMutation(
|
|
||||||
input: DatabaseNameFieldUpdateMutationInput
|
|
||||||
): Promise<DatabaseNameFieldUpdateMutationOutput> {
|
|
||||||
const workspace = this.getWorkspace(input.userId);
|
|
||||||
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
|
|
||||||
input.databaseId,
|
|
||||||
(attributes) => {
|
|
||||||
attributes.nameField = {
|
|
||||||
name: input.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result === 'unauthorized') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.DatabaseUpdateForbidden,
|
|
||||||
"You don't have permission to update this database."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result !== 'success') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.DatabaseUpdateFailed,
|
|
||||||
'Something went wrong while updating the database.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
|
||||||
import { MutationHandler } from '@colanode/client/lib/types';
|
|
||||||
import { fetchNode } from '@colanode/client/lib/utils';
|
|
||||||
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
|
|
||||||
import {
|
|
||||||
FieldCreateMutationInput,
|
|
||||||
FieldCreateMutationOutput,
|
|
||||||
} from '@colanode/client/mutations/databases/field-create';
|
|
||||||
import {
|
|
||||||
compareString,
|
|
||||||
DatabaseAttributes,
|
|
||||||
FieldAttributes,
|
|
||||||
FieldType,
|
|
||||||
generateId,
|
|
||||||
generateFractionalIndex,
|
|
||||||
IdType,
|
|
||||||
} from '@colanode/core';
|
|
||||||
|
|
||||||
export class FieldCreateMutationHandler
|
|
||||||
extends WorkspaceMutationHandlerBase
|
|
||||||
implements MutationHandler<FieldCreateMutationInput>
|
|
||||||
{
|
|
||||||
async handleMutation(
|
|
||||||
input: FieldCreateMutationInput
|
|
||||||
): Promise<FieldCreateMutationOutput> {
|
|
||||||
const workspace = this.getWorkspace(input.userId);
|
|
||||||
|
|
||||||
if (input.fieldType === 'relation') {
|
|
||||||
if (!input.relationDatabaseId) {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.RelationDatabaseNotFound,
|
|
||||||
'Relation database not found.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const relationDatabase = await fetchNode(
|
|
||||||
workspace.database,
|
|
||||||
input.relationDatabaseId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!relationDatabase || relationDatabase.type !== 'database') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.RelationDatabaseNotFound,
|
|
||||||
'Relation database not found.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldId = generateId(IdType.Field);
|
|
||||||
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
|
|
||||||
input.databaseId,
|
|
||||||
(attributes) => {
|
|
||||||
const maxIndex = Object.values(attributes.fields)
|
|
||||||
.map((field) => field.index)
|
|
||||||
.sort((a, b) => -compareString(a, b))[0];
|
|
||||||
|
|
||||||
const index = generateFractionalIndex(maxIndex, null);
|
|
||||||
|
|
||||||
const newField: FieldAttributes = {
|
|
||||||
id: fieldId,
|
|
||||||
type: input.fieldType as FieldType,
|
|
||||||
name: input.name,
|
|
||||||
index,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (newField.type === 'relation') {
|
|
||||||
newField.databaseId = input.relationDatabaseId;
|
|
||||||
}
|
|
||||||
|
|
||||||
attributes.fields[fieldId] = newField;
|
|
||||||
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result === 'unauthorized') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.FieldCreateForbidden,
|
|
||||||
"You don't have permission to create a field in this database."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result !== 'success') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.FieldCreateFailed,
|
|
||||||
'Something went wrong while creating the field.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: fieldId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
|
||||||
import { MutationHandler } from '@colanode/client/lib/types';
|
|
||||||
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
|
|
||||||
import {
|
|
||||||
FieldDeleteMutationInput,
|
|
||||||
FieldDeleteMutationOutput,
|
|
||||||
} from '@colanode/client/mutations/databases/field-delete';
|
|
||||||
import { DatabaseAttributes } from '@colanode/core';
|
|
||||||
|
|
||||||
export class FieldDeleteMutationHandler
|
|
||||||
extends WorkspaceMutationHandlerBase
|
|
||||||
implements MutationHandler<FieldDeleteMutationInput>
|
|
||||||
{
|
|
||||||
async handleMutation(
|
|
||||||
input: FieldDeleteMutationInput
|
|
||||||
): Promise<FieldDeleteMutationOutput> {
|
|
||||||
const workspace = this.getWorkspace(input.userId);
|
|
||||||
|
|
||||||
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
|
|
||||||
input.databaseId,
|
|
||||||
(attributes) => {
|
|
||||||
delete attributes.fields[input.fieldId];
|
|
||||||
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result === 'unauthorized') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.FieldDeleteForbidden,
|
|
||||||
"You don't have permission to delete this field."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result !== 'success') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.FieldDeleteFailed,
|
|
||||||
'Something went wrong while deleting the field.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: input.fieldId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
|
||||||
import { MutationHandler } from '@colanode/client/lib/types';
|
|
||||||
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
|
|
||||||
import {
|
|
||||||
FieldNameUpdateMutationInput,
|
|
||||||
FieldNameUpdateMutationOutput,
|
|
||||||
} from '@colanode/client/mutations/databases/field-name-update';
|
|
||||||
import { DatabaseAttributes } from '@colanode/core';
|
|
||||||
|
|
||||||
export class FieldNameUpdateMutationHandler
|
|
||||||
extends WorkspaceMutationHandlerBase
|
|
||||||
implements MutationHandler<FieldNameUpdateMutationInput>
|
|
||||||
{
|
|
||||||
async handleMutation(
|
|
||||||
input: FieldNameUpdateMutationInput
|
|
||||||
): Promise<FieldNameUpdateMutationOutput> {
|
|
||||||
const workspace = this.getWorkspace(input.userId);
|
|
||||||
|
|
||||||
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
|
|
||||||
input.databaseId,
|
|
||||||
(attributes) => {
|
|
||||||
const field = attributes.fields[input.fieldId];
|
|
||||||
if (!field) {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.FieldNotFound,
|
|
||||||
'The field you are trying to update does not exist.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
field.name = input.name;
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result === 'unauthorized') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.FieldUpdateForbidden,
|
|
||||||
"You don't have permission to update this field."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result !== 'success') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.FieldUpdateFailed,
|
|
||||||
'Something went wrong while updating the field.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: input.fieldId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,10 +17,6 @@ import { EmailVerifyMutationHandler } from './auth/email-verify';
|
|||||||
import { GoogleLoginMutationHandler } from './auth/google-login';
|
import { GoogleLoginMutationHandler } from './auth/google-login';
|
||||||
import { AvatarUploadMutationHandler } from './avatars/avatar-upload';
|
import { AvatarUploadMutationHandler } from './avatars/avatar-upload';
|
||||||
import { ChatCreateMutationHandler } from './chats/chat-create';
|
import { ChatCreateMutationHandler } from './chats/chat-create';
|
||||||
import { DatabaseNameFieldUpdateMutationHandler } from './databases/database-name-field-update';
|
|
||||||
import { FieldCreateMutationHandler } from './databases/field-create';
|
|
||||||
import { FieldDeleteMutationHandler } from './databases/field-delete';
|
|
||||||
import { FieldNameUpdateMutationHandler } from './databases/field-name-update';
|
|
||||||
import { SelectOptionCreateMutationHandler } from './databases/select-option-create';
|
import { SelectOptionCreateMutationHandler } from './databases/select-option-create';
|
||||||
import { SelectOptionDeleteMutationHandler } from './databases/select-option-delete';
|
import { SelectOptionDeleteMutationHandler } from './databases/select-option-delete';
|
||||||
import { SelectOptionUpdateMutationHandler } from './databases/select-option-update';
|
import { SelectOptionUpdateMutationHandler } from './databases/select-option-update';
|
||||||
@@ -67,12 +63,6 @@ export const buildMutationHandlerMap = (
|
|||||||
'node.create': new NodeCreateMutationHandler(app),
|
'node.create': new NodeCreateMutationHandler(app),
|
||||||
'node.update': new NodeUpdateMutationHandler(app),
|
'node.update': new NodeUpdateMutationHandler(app),
|
||||||
'chat.create': new ChatCreateMutationHandler(app),
|
'chat.create': new ChatCreateMutationHandler(app),
|
||||||
'database.name.field.update': new DatabaseNameFieldUpdateMutationHandler(
|
|
||||||
app
|
|
||||||
),
|
|
||||||
'field.create': new FieldCreateMutationHandler(app),
|
|
||||||
'field.delete': new FieldDeleteMutationHandler(app),
|
|
||||||
'field.name.update': new FieldNameUpdateMutationHandler(app),
|
|
||||||
'message.create': new MessageCreateMutationHandler(app),
|
'message.create': new MessageCreateMutationHandler(app),
|
||||||
'node.collaborator.create': new NodeCollaboratorCreateMutationHandler(app),
|
'node.collaborator.create': new NodeCollaboratorCreateMutationHandler(app),
|
||||||
'node.collaborator.delete': new NodeCollaboratorDeleteMutationHandler(app),
|
'node.collaborator.delete': new NodeCollaboratorDeleteMutationHandler(app),
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
export type DatabaseNameFieldUpdateMutationInput = {
|
|
||||||
type: 'database.name.field.update';
|
|
||||||
userId: string;
|
|
||||||
databaseId: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DatabaseNameFieldUpdateMutationOutput = {
|
|
||||||
success: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare module '@colanode/client/mutations' {
|
|
||||||
interface MutationMap {
|
|
||||||
'database.name.field.update': {
|
|
||||||
input: DatabaseNameFieldUpdateMutationInput;
|
|
||||||
output: DatabaseNameFieldUpdateMutationOutput;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { FieldType } from '@colanode/core';
|
|
||||||
|
|
||||||
export type FieldCreateMutationInput = {
|
|
||||||
type: 'field.create';
|
|
||||||
userId: string;
|
|
||||||
databaseId: string;
|
|
||||||
name: string;
|
|
||||||
fieldType: FieldType;
|
|
||||||
relationDatabaseId?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FieldCreateMutationOutput = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare module '@colanode/client/mutations' {
|
|
||||||
interface MutationMap {
|
|
||||||
'field.create': {
|
|
||||||
input: FieldCreateMutationInput;
|
|
||||||
output: FieldCreateMutationOutput;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
export type FieldDeleteMutationInput = {
|
|
||||||
type: 'field.delete';
|
|
||||||
userId: string;
|
|
||||||
databaseId: string;
|
|
||||||
fieldId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FieldDeleteMutationOutput = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare module '@colanode/client/mutations' {
|
|
||||||
interface MutationMap {
|
|
||||||
'field.delete': {
|
|
||||||
input: FieldDeleteMutationInput;
|
|
||||||
output: FieldDeleteMutationOutput;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
export type FieldNameUpdateMutationInput = {
|
|
||||||
type: 'field.name.update';
|
|
||||||
userId: string;
|
|
||||||
databaseId: string;
|
|
||||||
fieldId: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FieldNameUpdateMutationOutput = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare module '@colanode/client/mutations' {
|
|
||||||
interface MutationMap {
|
|
||||||
'field.name.update': {
|
|
||||||
input: FieldNameUpdateMutationInput;
|
|
||||||
output: FieldNameUpdateMutationOutput;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,14 +10,10 @@ export * from './apps/metadata-delete';
|
|||||||
export * from './apps/metadata-update';
|
export * from './apps/metadata-update';
|
||||||
export * from './avatars/avatar-upload';
|
export * from './avatars/avatar-upload';
|
||||||
export * from './chats/chat-create';
|
export * from './chats/chat-create';
|
||||||
export * from './databases/field-create';
|
|
||||||
export * from './databases/field-delete';
|
|
||||||
export * from './databases/field-name-update';
|
|
||||||
export * from './databases/select-option-create';
|
export * from './databases/select-option-create';
|
||||||
export * from './databases/select-option-delete';
|
export * from './databases/select-option-delete';
|
||||||
export * from './databases/select-option-update';
|
export * from './databases/select-option-update';
|
||||||
export * from './databases/view-update';
|
export * from './databases/view-update';
|
||||||
export * from './databases/database-name-field-update';
|
|
||||||
export * from './documents/document-update';
|
export * from './documents/document-update';
|
||||||
export * from './files/file-create';
|
export * from './files/file-create';
|
||||||
export * from './files/file-download';
|
export * from './files/file-download';
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const DatabaseCreateDialog = ({
|
|||||||
name: values.name,
|
name: values.name,
|
||||||
parentId: spaceId,
|
parentId: spaceId,
|
||||||
fields: {
|
fields: {
|
||||||
[generateId(IdType.Field)]: {
|
[fieldId]: {
|
||||||
id: fieldId,
|
id: fieldId,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
index: generateFractionalIndex(null, null),
|
index: generateFractionalIndex(null, null),
|
||||||
|
|||||||
@@ -28,64 +28,6 @@ export const Database = ({ database, role, children }: DatabaseProps) => {
|
|||||||
canEdit,
|
canEdit,
|
||||||
canCreateRecord,
|
canCreateRecord,
|
||||||
rootId: database.rootId,
|
rootId: database.rootId,
|
||||||
createField: async (type, name) => {
|
|
||||||
if (!canEdit) return;
|
|
||||||
|
|
||||||
const result = await window.colanode.executeMutation({
|
|
||||||
type: 'field.create',
|
|
||||||
databaseId: database.id,
|
|
||||||
name,
|
|
||||||
fieldType: type,
|
|
||||||
userId: workspace.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
toast.error(result.error.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renameField: async (id, name) => {
|
|
||||||
if (!canEdit) return;
|
|
||||||
|
|
||||||
const result = await window.colanode.executeMutation({
|
|
||||||
type: 'field.name.update',
|
|
||||||
databaseId: database.id,
|
|
||||||
fieldId: id,
|
|
||||||
name,
|
|
||||||
userId: workspace.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
toast.error(result.error.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateNameField: async (name) => {
|
|
||||||
if (!canEdit) return;
|
|
||||||
|
|
||||||
const result = await window.colanode.executeMutation({
|
|
||||||
type: 'database.name.field.update',
|
|
||||||
databaseId: database.id,
|
|
||||||
name,
|
|
||||||
userId: workspace.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
toast.error(result.error.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteField: async (id) => {
|
|
||||||
if (!canEdit) return;
|
|
||||||
|
|
||||||
const result = await window.colanode.executeMutation({
|
|
||||||
type: 'field.delete',
|
|
||||||
databaseId: database.id,
|
|
||||||
fieldId: id,
|
|
||||||
userId: workspace.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
toast.error(result.error.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createSelectOption: async (fieldId, name, color) => {
|
createSelectOption: async (fieldId, name, color) => {
|
||||||
if (!canEdit) return;
|
if (!canEdit) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod/v4';
|
import { z } from 'zod/v4';
|
||||||
|
|
||||||
import { FieldType } from '@colanode/core';
|
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
|
||||||
|
import {
|
||||||
|
compareString,
|
||||||
|
FieldAttributes,
|
||||||
|
FieldType,
|
||||||
|
generateFractionalIndex,
|
||||||
|
generateId,
|
||||||
|
IdType,
|
||||||
|
} from '@colanode/core';
|
||||||
|
import { collections } from '@colanode/ui/collections';
|
||||||
import { DatabaseSelect } from '@colanode/ui/components/databases/database-select';
|
import { DatabaseSelect } from '@colanode/ui/components/databases/database-select';
|
||||||
import { FieldTypeSelect } from '@colanode/ui/components/databases/fields/field-type-select';
|
import { FieldTypeSelect } from '@colanode/ui/components/databases/fields/field-type-select';
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
@@ -25,7 +35,6 @@ import {
|
|||||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
||||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(1, { message: 'Name is required' }),
|
name: z.string().min(1, { message: 'Name is required' }),
|
||||||
@@ -50,6 +59,8 @@ const formSchema = z.object({
|
|||||||
relationDatabaseId: z.string().optional().nullable(),
|
relationDatabaseId: z.string().optional().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type FieldCreateFormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
interface FieldCreatePopoverProps {
|
interface FieldCreatePopoverProps {
|
||||||
button: React.ReactNode;
|
button: React.ReactNode;
|
||||||
onSuccess?: (fieldId: string) => void;
|
onSuccess?: (fieldId: string) => void;
|
||||||
@@ -65,9 +76,7 @@ export const FieldCreatePopover = ({
|
|||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const database = useDatabase();
|
const database = useDatabase();
|
||||||
|
|
||||||
const { mutate, isPending } = useMutation();
|
const form = useForm<FieldCreateFormValues>({
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -82,26 +91,71 @@ export const FieldCreatePopover = ({
|
|||||||
form.reset();
|
form.reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (values: z.infer<typeof formSchema>) => {
|
const { mutate, isPending } = useMutation({
|
||||||
mutate({
|
mutationFn: async (values: FieldCreateFormValues) => {
|
||||||
input: {
|
const nodes = collections.workspace(workspace.userId).nodes;
|
||||||
type: 'field.create',
|
|
||||||
databaseId: database.id,
|
if (values.type === 'relation') {
|
||||||
|
if (!values.relationDatabaseId) {
|
||||||
|
throw new MutationError(
|
||||||
|
MutationErrorCode.RelationDatabaseNotFound,
|
||||||
|
'Relation database not found.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relationDatabase = nodes.get(values.relationDatabaseId);
|
||||||
|
if (!relationDatabase || relationDatabase.type !== 'database') {
|
||||||
|
throw new MutationError(
|
||||||
|
MutationErrorCode.RelationDatabaseNotFound,
|
||||||
|
'Relation database not found.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nodes.has(database.id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldId = generateId(IdType.Field);
|
||||||
|
nodes.update(database.id, (draft) => {
|
||||||
|
if (draft.type !== 'database') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxIndex = Object.values(draft.fields)
|
||||||
|
.map((field) => field.index)
|
||||||
|
.sort((a, b) => -compareString(a, b))[0];
|
||||||
|
|
||||||
|
const index = generateFractionalIndex(maxIndex, null);
|
||||||
|
|
||||||
|
const newField: FieldAttributes = {
|
||||||
|
id: fieldId,
|
||||||
|
type: values.type as FieldType,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
fieldType: values.type,
|
index,
|
||||||
userId: workspace.userId,
|
};
|
||||||
relationDatabaseId: values.relationDatabaseId,
|
|
||||||
|
if (newField.type === 'relation') {
|
||||||
|
newField.databaseId = values.relationDatabaseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.fields[fieldId] = newField;
|
||||||
|
});
|
||||||
|
|
||||||
|
return fieldId;
|
||||||
},
|
},
|
||||||
onSuccess: (output) => {
|
onSuccess: (fieldId) => {
|
||||||
setOpen(false);
|
|
||||||
form.reset();
|
form.reset();
|
||||||
onSuccess?.(output.id);
|
setOpen(false);
|
||||||
|
|
||||||
|
if (fieldId) {
|
||||||
|
onSuccess?.(fieldId);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError: (error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message as string);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if (!database.canEdit) {
|
if (!database.canEdit) {
|
||||||
return null;
|
return null;
|
||||||
@@ -114,7 +168,7 @@ export const FieldCreatePopover = ({
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
className="flex flex-col gap-2"
|
className="flex flex-col gap-2"
|
||||||
onSubmit={form.handleSubmit(handleSubmit)}
|
onSubmit={form.handleSubmit((values) => mutate(values))}
|
||||||
>
|
>
|
||||||
<div className="grow space-y-4 py-2 pb-4">
|
<div className="grow space-y-4 py-2 pb-4">
|
||||||
<FormField
|
<FormField
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { collections } from '@colanode/ui/collections';
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogCancel,
|
AlertDialogCancel,
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
} from '@colanode/ui/components/ui/alert-dialog';
|
} from '@colanode/ui/components/ui/alert-dialog';
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
|
||||||
interface FieldDeleteDialogProps {
|
interface FieldDeleteDialogProps {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,6 +23,7 @@ export const FieldDeleteDialog = ({
|
|||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: FieldDeleteDialogProps) => {
|
}: FieldDeleteDialogProps) => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
const database = useDatabase();
|
const database = useDatabase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -40,7 +43,15 @@ export const FieldDeleteDialog = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
database.deleteField(id);
|
const nodes = collections.workspace(workspace.userId).nodes;
|
||||||
|
nodes.update(database.id, (draft) => {
|
||||||
|
if (draft.type !== 'database') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { [id]: _removed, ...rest } = draft.fields;
|
||||||
|
draft.fields = rest;
|
||||||
|
});
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { FieldAttributes } from '@colanode/core';
|
import { FieldAttributes } from '@colanode/core';
|
||||||
|
import { collections } from '@colanode/ui/collections';
|
||||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
||||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
|
||||||
interface FieldRenameInputProps {
|
interface FieldRenameInputProps {
|
||||||
field: FieldAttributes;
|
field: FieldAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
const database = useDatabase();
|
const database = useDatabase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +19,20 @@ export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
|||||||
readOnly={!database.canEdit}
|
readOnly={!database.canEdit}
|
||||||
onChange={(newName) => {
|
onChange={(newName) => {
|
||||||
if (newName === field.name) return;
|
if (newName === field.name) return;
|
||||||
database.renameField(field.id, newName);
|
|
||||||
|
const nodes = collections.workspace(workspace.userId).nodes;
|
||||||
|
nodes.update(database.id, (draft) => {
|
||||||
|
if (draft.type !== 'database') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldAttributes = draft.fields[field.id];
|
||||||
|
if (!fieldAttributes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldAttributes.name = newName;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Fragment, useRef, useState } from 'react';
|
|||||||
import { useDrop } from 'react-dnd';
|
import { useDrop } from 'react-dnd';
|
||||||
|
|
||||||
import { SpecialId } from '@colanode/core';
|
import { SpecialId } from '@colanode/core';
|
||||||
|
import { collections } from '@colanode/ui/collections';
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@@ -13,9 +14,11 @@ import { Separator } from '@colanode/ui/components/ui/separator';
|
|||||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
||||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
import { cn } from '@colanode/ui/lib/utils';
|
import { cn } from '@colanode/ui/lib/utils';
|
||||||
|
|
||||||
export const TableViewNameHeader = () => {
|
export const TableViewNameHeader = () => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
const database = useDatabase();
|
const database = useDatabase();
|
||||||
const view = useDatabaseView();
|
const view = useDatabaseView();
|
||||||
|
|
||||||
@@ -90,7 +93,14 @@ export const TableViewNameHeader = () => {
|
|||||||
readOnly={!database.canEdit}
|
readOnly={!database.canEdit}
|
||||||
onChange={(newName) => {
|
onChange={(newName) => {
|
||||||
if (newName === database.nameField?.name) return;
|
if (newName === database.nameField?.name) return;
|
||||||
database.updateNameField(newName);
|
const nodes = collections.workspace(workspace.userId).nodes;
|
||||||
|
nodes.update(database.id, (draft) => {
|
||||||
|
if (draft.type !== 'database') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.nameField = { name: newName };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { createContext, useContext } from 'react';
|
|||||||
import {
|
import {
|
||||||
DatabaseNameFieldAttributes,
|
DatabaseNameFieldAttributes,
|
||||||
FieldAttributes,
|
FieldAttributes,
|
||||||
FieldType,
|
|
||||||
NodeRole,
|
NodeRole,
|
||||||
SelectOptionAttributes,
|
SelectOptionAttributes,
|
||||||
} from '@colanode/core';
|
} from '@colanode/core';
|
||||||
@@ -17,10 +16,6 @@ interface DatabaseContext {
|
|||||||
canCreateRecord: boolean;
|
canCreateRecord: boolean;
|
||||||
role: NodeRole;
|
role: NodeRole;
|
||||||
rootId: string;
|
rootId: string;
|
||||||
createField: (type: FieldType, name: string) => void;
|
|
||||||
renameField: (id: string, name: string) => void;
|
|
||||||
updateNameField: (name: string) => void;
|
|
||||||
deleteField: (id: string) => void;
|
|
||||||
createSelectOption: (fieldId: string, name: string, color: string) => void;
|
createSelectOption: (fieldId: string, name: string, color: string) => void;
|
||||||
updateSelectOption: (
|
updateSelectOption: (
|
||||||
fieldId: string,
|
fieldId: string,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const DatabaseInlineCommand: EditorCommand = {
|
|||||||
name: 'Untitled',
|
name: 'Untitled',
|
||||||
parentId: documentId,
|
parentId: documentId,
|
||||||
fields: {
|
fields: {
|
||||||
[generateId(IdType.Field)]: {
|
[fieldId]: {
|
||||||
id: fieldId,
|
id: fieldId,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
index: generateFractionalIndex(null, null),
|
index: generateFractionalIndex(null, null),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const DatabaseCommand: EditorCommand = {
|
|||||||
name: 'Untitled',
|
name: 'Untitled',
|
||||||
parentId: documentId,
|
parentId: documentId,
|
||||||
fields: {
|
fields: {
|
||||||
[generateId(IdType.Field)]: {
|
[fieldId]: {
|
||||||
id: fieldId,
|
id: fieldId,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
index: generateFractionalIndex(null, null),
|
index: generateFractionalIndex(null, null),
|
||||||
|
|||||||
Reference in New Issue
Block a user