mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 03:37:51 +01:00
Use tanstackdb for node updates
This commit is contained in:
@@ -1,61 +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 {
|
||||
ChannelUpdateMutationInput,
|
||||
ChannelUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/channels/channel-update';
|
||||
import { ChannelAttributes } from '@colanode/core';
|
||||
|
||||
export class ChannelUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ChannelUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ChannelUpdateMutationInput
|
||||
): Promise<ChannelUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<ChannelAttributes>(
|
||||
input.channelId,
|
||||
(attributes: ChannelAttributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'not_found') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.ChannelNotFound,
|
||||
'Channel not found or has been deleted.'
|
||||
);
|
||||
}
|
||||
|
||||
if (result === 'invalid_attributes') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.ChannelUpdateFailed,
|
||||
'Something went wrong while updating the channel.'
|
||||
);
|
||||
}
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.ChannelUpdateForbidden,
|
||||
"You don't have permission to update this channel."
|
||||
);
|
||||
}
|
||||
|
||||
if (result === 'success') {
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
throw new MutationError(
|
||||
MutationErrorCode.Unknown,
|
||||
'Something went wrong while updating the channel.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,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 {
|
||||
DatabaseUpdateMutationInput,
|
||||
DatabaseUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/database-update';
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
export class DatabaseUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<DatabaseUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: DatabaseUpdateMutationInput
|
||||
): Promise<DatabaseUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,83 +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 {
|
||||
SelectOptionCreateMutationInput,
|
||||
SelectOptionCreateMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/select-option-create';
|
||||
import {
|
||||
compareString,
|
||||
DatabaseAttributes,
|
||||
generateId,
|
||||
generateFractionalIndex,
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
export class SelectOptionCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SelectOptionCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SelectOptionCreateMutationInput
|
||||
): Promise<SelectOptionCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const id = generateId(IdType.SelectOption);
|
||||
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 create a select option in does not exist.'
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type !== 'multi_select' && field.type !== 'select') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FieldTypeInvalid,
|
||||
'The field you are trying to create a select option in is not a "Select" or "Multi-Select" field.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!field.options) {
|
||||
field.options = {};
|
||||
}
|
||||
|
||||
const maxIndex = Object.values(field.options)
|
||||
.map((selectOption) => selectOption.index)
|
||||
.sort((a, b) => -compareString(a, b))[0];
|
||||
|
||||
const index = generateFractionalIndex(maxIndex, null);
|
||||
|
||||
field.options[id] = {
|
||||
name: input.name,
|
||||
id: id,
|
||||
color: input.color,
|
||||
index: index,
|
||||
};
|
||||
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionCreateForbidden,
|
||||
"You don't have permission to create a select option in this field."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionCreateFailed,
|
||||
'Something went wrong while creating the select option.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: id,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,75 +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 {
|
||||
SelectOptionDeleteMutationInput,
|
||||
SelectOptionDeleteMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/select-option-delete';
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
export class SelectOptionDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SelectOptionDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SelectOptionDeleteMutationInput
|
||||
): Promise<SelectOptionDeleteMutationOutput> {
|
||||
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 delete a select option from does not exist.'
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type !== 'multi_select' && field.type !== 'select') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FieldTypeInvalid,
|
||||
'The field you are trying to delete a select option from is not a "Select" or "Multi-Select" field.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!field.options) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionNotFound,
|
||||
'The field you are trying to delete a select option from does not have any select options.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!field.options[input.optionId]) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionNotFound,
|
||||
'The select option you are trying to delete does not exist.'
|
||||
);
|
||||
}
|
||||
|
||||
delete field.options[input.optionId];
|
||||
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionDeleteForbidden,
|
||||
"You don't have permission to delete this select option."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionDeleteFailed,
|
||||
'Something went wrong while deleting the select option.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: input.optionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,73 +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 {
|
||||
SelectOptionUpdateMutationInput,
|
||||
SelectOptionUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/select-option-update';
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
export class SelectOptionUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SelectOptionUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SelectOptionUpdateMutationInput
|
||||
): Promise<SelectOptionUpdateMutationOutput> {
|
||||
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 a select option in does not exist.'
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type !== 'multi_select' && field.type !== 'select') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FieldTypeInvalid,
|
||||
'The field you are trying to update a select option in is not a "Select" or "Multi-Select" field.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!field.options) {
|
||||
field.options = {};
|
||||
}
|
||||
|
||||
const option = field.options[input.optionId];
|
||||
if (!option) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionNotFound,
|
||||
'The select option you are trying to update does not exist.'
|
||||
);
|
||||
}
|
||||
|
||||
option.name = input.name;
|
||||
option.color = input.color;
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionUpdateForbidden,
|
||||
"You don't have permission to update this select option."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SelectOptionUpdateFailed,
|
||||
'Something went wrong while updating the select option.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: input.optionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,44 +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 {
|
||||
ViewUpdateMutationInput,
|
||||
ViewUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/databases/view-update';
|
||||
import { DatabaseViewAttributes } from '@colanode/core';
|
||||
|
||||
export class ViewUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewUpdateMutationInput
|
||||
): Promise<ViewUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<DatabaseViewAttributes>(
|
||||
input.viewId,
|
||||
() => {
|
||||
return input.view;
|
||||
}
|
||||
);
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
||||
import { MutationHandler } from '@colanode/client/lib/types';
|
||||
import {
|
||||
MutationError,
|
||||
MutationErrorCode,
|
||||
FolderUpdateMutationInput,
|
||||
FolderUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations';
|
||||
import { FolderAttributes } from '@colanode/core';
|
||||
|
||||
export class FolderUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FolderUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FolderUpdateMutationInput
|
||||
): Promise<FolderUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<FolderAttributes>(
|
||||
input.folderId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FolderUpdateForbidden,
|
||||
"You don't have permission to update this folder."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FolderUpdateFailed,
|
||||
'There was an error while updating the folder. Please try again.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,23 +16,11 @@ import { TabCreateMutationHandler } from './apps/tab-create';
|
||||
import { TabDeleteMutationHandler } from './apps/tab-delete';
|
||||
import { TabUpdateMutationHandler } from './apps/tab-update';
|
||||
import { AvatarUploadMutationHandler } from './avatars/avatar-upload';
|
||||
import { ChannelUpdateMutationHandler } from './channels/channel-update';
|
||||
import { DatabaseNameFieldUpdateMutationHandler } from './databases/database-name-field-update';
|
||||
import { DatabaseUpdateMutationHandler } from './databases/database-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 { SelectOptionDeleteMutationHandler } from './databases/select-option-delete';
|
||||
import { SelectOptionUpdateMutationHandler } from './databases/select-option-update';
|
||||
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';
|
||||
import { FileDeleteMutationHandler } from './files/file-delete';
|
||||
import { FileDownloadMutationHandler } from './files/file-download';
|
||||
import { TempFileCreateMutationHandler } from './files/temp-file-create';
|
||||
import { FolderUpdateMutationHandler } from './folders/folder-update';
|
||||
import { NodeCollaboratorCreateMutationHandler } from './nodes/node-collaborator-create';
|
||||
import { NodeCollaboratorDeleteMutationHandler } from './nodes/node-collaborator-delete';
|
||||
import { NodeCollaboratorUpdateMutationHandler } from './nodes/node-collaborator-update';
|
||||
@@ -42,15 +30,9 @@ import { NodeInteractionOpenedMutationHandler } from './nodes/node-interaction-o
|
||||
import { NodeInteractionSeenMutationHandler } from './nodes/node-interaction-seen';
|
||||
import { NodeReactionCreateMutationHandler } from './nodes/node-reaction-create';
|
||||
import { NodeReactionDeleteMutationHandler } from './nodes/node-reaction-delete';
|
||||
import { PageUpdateMutationHandler } from './pages/page-update';
|
||||
import { RecordAvatarUpdateMutationHandler } from './records/record-avatar-update';
|
||||
import { RecordFieldValueDeleteMutationHandler } from './records/record-field-value-delete';
|
||||
import { RecordFieldValueSetMutationHandler } from './records/record-field-value-set';
|
||||
import { RecordNameUpdateMutationHandler } from './records/record-name-update';
|
||||
import { NodeUpdateMutationHandler } from './nodes/node-update';
|
||||
import { ServerCreateMutationHandler } from './servers/server-create';
|
||||
import { ServerDeleteMutationHandler } from './servers/server-delete';
|
||||
import { SpaceChildReorderMutationHandler } from './spaces/space-child-reorder';
|
||||
import { SpaceUpdateMutationHandler } from './spaces/space-update';
|
||||
import { UserRoleUpdateMutationHandler } from './users/user-role-update';
|
||||
import { UserStorageUpdateMutationHandler } from './users/user-storage-update';
|
||||
import { UsersCreateMutationHandler } from './users/users-create';
|
||||
@@ -71,12 +53,6 @@ export const buildMutationHandlerMap = (
|
||||
'email.register': new EmailRegisterMutationHandler(app),
|
||||
'email.verify': new EmailVerifyMutationHandler(app),
|
||||
'google.login': new GoogleLoginMutationHandler(app),
|
||||
'database.name.field.update': new DatabaseNameFieldUpdateMutationHandler(
|
||||
app
|
||||
),
|
||||
'field.create': new FieldCreateMutationHandler(app),
|
||||
'field.delete': new FieldDeleteMutationHandler(app),
|
||||
'field.name.update': new FieldNameUpdateMutationHandler(app),
|
||||
'file.delete': new FileDeleteMutationHandler(app),
|
||||
'node.collaborator.create': new NodeCollaboratorCreateMutationHandler(app),
|
||||
'node.collaborator.delete': new NodeCollaboratorDeleteMutationHandler(app),
|
||||
@@ -85,13 +61,6 @@ export const buildMutationHandlerMap = (
|
||||
'node.interaction.seen': new NodeInteractionSeenMutationHandler(app),
|
||||
'node.reaction.create': new NodeReactionCreateMutationHandler(app),
|
||||
'node.reaction.delete': new NodeReactionDeleteMutationHandler(app),
|
||||
'record.avatar.update': new RecordAvatarUpdateMutationHandler(app),
|
||||
'record.name.update': new RecordNameUpdateMutationHandler(app),
|
||||
'record.field.value.delete': new RecordFieldValueDeleteMutationHandler(app),
|
||||
'record.field.value.set': new RecordFieldValueSetMutationHandler(app),
|
||||
'select.option.create': new SelectOptionCreateMutationHandler(app),
|
||||
'select.option.delete': new SelectOptionDeleteMutationHandler(app),
|
||||
'select.option.update': new SelectOptionUpdateMutationHandler(app),
|
||||
'server.create': new ServerCreateMutationHandler(app),
|
||||
'server.delete': new ServerDeleteMutationHandler(app),
|
||||
'user.role.update': new UserRoleUpdateMutationHandler(app),
|
||||
@@ -102,15 +71,7 @@ export const buildMutationHandlerMap = (
|
||||
'account.logout': new AccountLogoutMutationHandler(app),
|
||||
'file.create': new FileCreateMutationHandler(app),
|
||||
'file.download': new FileDownloadMutationHandler(app),
|
||||
'space.update': new SpaceUpdateMutationHandler(app),
|
||||
'space.child.reorder': new SpaceChildReorderMutationHandler(app),
|
||||
'account.update': new AccountUpdateMutationHandler(app),
|
||||
'view.update': new ViewUpdateMutationHandler(app),
|
||||
'view.name.update': new ViewNameUpdateMutationHandler(app),
|
||||
'channel.update': new ChannelUpdateMutationHandler(app),
|
||||
'page.update': new PageUpdateMutationHandler(app),
|
||||
'folder.update': new FolderUpdateMutationHandler(app),
|
||||
'database.update': new DatabaseUpdateMutationHandler(app),
|
||||
'document.update': new DocumentUpdateMutationHandler(app),
|
||||
'metadata.update': new MetadataUpdateMutationHandler(app),
|
||||
'metadata.delete': new MetadataDeleteMutationHandler(app),
|
||||
@@ -124,5 +85,6 @@ export const buildMutationHandlerMap = (
|
||||
'tab.update': new TabUpdateMutationHandler(app),
|
||||
'tab.delete': new TabDeleteMutationHandler(app),
|
||||
'node.delete': new NodeDeleteMutationHandler(app),
|
||||
'node.update': new NodeUpdateMutationHandler(app),
|
||||
};
|
||||
};
|
||||
|
||||
26
packages/client/src/handlers/mutations/nodes/node-update.ts
Normal file
26
packages/client/src/handlers/mutations/nodes/node-update.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { merge } from 'lodash-es';
|
||||
|
||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
||||
import { MutationHandler } from '@colanode/client/lib/types';
|
||||
import {
|
||||
NodeUpdateMutationInput,
|
||||
NodeUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/nodes/node-update';
|
||||
|
||||
export class NodeUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<NodeUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: NodeUpdateMutationInput
|
||||
): Promise<NodeUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
await workspace.nodes.updateNode(input.nodeId, (attributes) => {
|
||||
return merge(attributes, input.attributes);
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
PageUpdateMutationInput,
|
||||
PageUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/pages/page-update';
|
||||
import { PageAttributes } from '@colanode/core';
|
||||
|
||||
export class PageUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<PageUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: PageUpdateMutationInput
|
||||
): Promise<PageUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<PageAttributes>(
|
||||
input.pageId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.PageUpdateForbidden,
|
||||
"You don't have permission to update this page."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.PageUpdateFailed,
|
||||
'Something went wrong while updating the page. Please try again later.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,38 +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 {
|
||||
RecordAvatarUpdateMutationInput,
|
||||
RecordAvatarUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/records/record-avatar-update';
|
||||
import { RecordAttributes } from '@colanode/core';
|
||||
|
||||
export class RecordAvatarUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordAvatarUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordAvatarUpdateMutationInput
|
||||
): Promise<RecordAvatarUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<RecordAttributes>(
|
||||
input.recordId,
|
||||
(attributes) => {
|
||||
attributes.avatar = input.avatar;
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RecordUpdateForbidden,
|
||||
"You don't have permission to update this record."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +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 {
|
||||
RecordFieldValueDeleteMutationInput,
|
||||
RecordFieldValueDeleteMutationOutput,
|
||||
} from '@colanode/client/mutations/records/record-field-value-delete';
|
||||
import { RecordAttributes } from '@colanode/core';
|
||||
|
||||
export class RecordFieldValueDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordFieldValueDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordFieldValueDeleteMutationInput
|
||||
): Promise<RecordFieldValueDeleteMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<RecordAttributes>(
|
||||
input.recordId,
|
||||
(attributes) => {
|
||||
delete attributes.fields[input.fieldId];
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RecordUpdateForbidden,
|
||||
"You don't have permission to delete this field value."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RecordUpdateFailed,
|
||||
'Something went wrong while deleting the field value. Please try again later.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +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 {
|
||||
RecordFieldValueSetMutationInput,
|
||||
RecordFieldValueSetMutationOutput,
|
||||
} from '@colanode/client/mutations/records/record-field-value-set';
|
||||
import { RecordAttributes } from '@colanode/core';
|
||||
|
||||
export class RecordFieldValueSetMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordFieldValueSetMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordFieldValueSetMutationInput
|
||||
): Promise<RecordFieldValueSetMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<RecordAttributes>(
|
||||
input.recordId,
|
||||
(attributes) => {
|
||||
attributes.fields[input.fieldId] = input.value;
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RecordUpdateForbidden,
|
||||
"You don't have permission to set this field value."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RecordUpdateFailed,
|
||||
'Something went wrong while setting the field value.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +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 {
|
||||
RecordNameUpdateMutationInput,
|
||||
RecordNameUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/records/record-name-update';
|
||||
import { RecordAttributes } from '@colanode/core';
|
||||
|
||||
export class RecordNameUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordNameUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordNameUpdateMutationInput
|
||||
): Promise<RecordNameUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<RecordAttributes>(
|
||||
input.recordId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RecordUpdateForbidden,
|
||||
"You don't have permission to update this record."
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== 'success') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RecordUpdateFailed,
|
||||
'Something went wrong while updating the record name. Please try again later.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
import { SelectNode } from '@colanode/client/databases';
|
||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
||||
import { MutationHandler } from '@colanode/client/lib/types';
|
||||
import {
|
||||
MutationError,
|
||||
MutationErrorCode,
|
||||
SpaceChildReorderMutationInput,
|
||||
SpaceChildReorderMutationOutput,
|
||||
} from '@colanode/client/mutations';
|
||||
import {
|
||||
compareString,
|
||||
generateFractionalIndex,
|
||||
SpaceAttributes,
|
||||
} from '@colanode/core';
|
||||
|
||||
interface NodeFractionalIndex {
|
||||
id: string;
|
||||
defaultIndex: string;
|
||||
customIndex: string | null;
|
||||
}
|
||||
|
||||
export class SpaceChildReorderMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SpaceChildReorderMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SpaceChildReorderMutationInput
|
||||
): Promise<SpaceChildReorderMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
const children = await workspace.database
|
||||
.selectFrom('nodes')
|
||||
.where('parent_id', '=', input.spaceId)
|
||||
.orderBy('id')
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (children.length === 0) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SpaceUpdateFailed,
|
||||
'Space has no children.'
|
||||
);
|
||||
}
|
||||
|
||||
const result = await workspace.nodes.updateNode<SpaceAttributes>(
|
||||
input.spaceId,
|
||||
(attributes) => {
|
||||
const newIndex = this.generateSpaceChildIndex(
|
||||
attributes,
|
||||
children,
|
||||
input.childId,
|
||||
input.after
|
||||
);
|
||||
|
||||
if (!newIndex) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SpaceUpdateFailed,
|
||||
'Failed to generate new index.'
|
||||
);
|
||||
}
|
||||
|
||||
const childrenSettings = attributes.children ?? {};
|
||||
childrenSettings[input.childId] = {
|
||||
...(childrenSettings[input.childId] ?? {}),
|
||||
id: input.childId,
|
||||
index: newIndex,
|
||||
};
|
||||
|
||||
attributes.children = childrenSettings;
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SpaceUpdateForbidden,
|
||||
"You don't have permission to update this space."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
private generateSpaceChildIndex(
|
||||
attributes: SpaceAttributes,
|
||||
children: SelectNode[],
|
||||
childId: string,
|
||||
after: string | null
|
||||
): string | null {
|
||||
const child = children.find((c) => c.id === childId);
|
||||
if (!child) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sortedById = children.toSorted((a, b) => compareString(a.id, b.id));
|
||||
const indexes: NodeFractionalIndex[] = [];
|
||||
const childrenSettings = attributes.children ?? {};
|
||||
let lastIndex: string | null = null;
|
||||
|
||||
for (const child of sortedById) {
|
||||
lastIndex = generateFractionalIndex(lastIndex, null);
|
||||
indexes.push({
|
||||
id: child.id,
|
||||
defaultIndex: lastIndex,
|
||||
customIndex: childrenSettings[child.id]?.index ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
const sortedIndexes = indexes.sort((a, b) =>
|
||||
compareString(
|
||||
a.customIndex ?? a.defaultIndex,
|
||||
b.customIndex ?? b.defaultIndex
|
||||
)
|
||||
);
|
||||
|
||||
if (after === null) {
|
||||
const firstIndex = sortedIndexes[0];
|
||||
if (!firstIndex) {
|
||||
return generateFractionalIndex(null, null);
|
||||
}
|
||||
|
||||
const nextIndex = firstIndex.customIndex ?? firstIndex.defaultIndex;
|
||||
return generateFractionalIndex(null, nextIndex);
|
||||
}
|
||||
|
||||
const afterNodeIndex = sortedIndexes.findIndex((node) => node.id === after);
|
||||
if (afterNodeIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const afterNode = sortedIndexes[afterNodeIndex];
|
||||
if (!afterNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const previousIndex = afterNode.customIndex ?? afterNode.defaultIndex;
|
||||
let nextIndex: string | null = null;
|
||||
if (afterNodeIndex < sortedIndexes.length - 1) {
|
||||
const nextNode = sortedIndexes[afterNodeIndex + 1];
|
||||
if (!nextNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nextIndex = nextNode.customIndex ?? nextNode.defaultIndex;
|
||||
}
|
||||
|
||||
let newIndex = generateFractionalIndex(previousIndex, nextIndex);
|
||||
|
||||
const maxDefaultIndex = sortedIndexes
|
||||
.map((index) => index.defaultIndex)
|
||||
.sort((a, b) => -compareString(a, b))[0]!;
|
||||
|
||||
const newPotentialDefaultIndex = generateFractionalIndex(
|
||||
maxDefaultIndex,
|
||||
null
|
||||
);
|
||||
|
||||
if (newPotentialDefaultIndex === newIndex) {
|
||||
newIndex = generateFractionalIndex(
|
||||
previousIndex,
|
||||
newPotentialDefaultIndex
|
||||
);
|
||||
}
|
||||
|
||||
return newIndex;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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 {
|
||||
SpaceUpdateMutationInput,
|
||||
SpaceUpdateMutationOutput,
|
||||
} from '@colanode/client/mutations/spaces/space-update';
|
||||
import { SpaceAttributes } from '@colanode/core';
|
||||
|
||||
export class SpaceUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SpaceUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SpaceUpdateMutationInput
|
||||
): Promise<SpaceUpdateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.userId);
|
||||
|
||||
const result = await workspace.nodes.updateNode<SpaceAttributes>(
|
||||
input.spaceId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.description = input.description;
|
||||
attributes.avatar = input.avatar;
|
||||
return attributes;
|
||||
}
|
||||
);
|
||||
|
||||
if (result === 'unauthorized') {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.SpaceUpdateForbidden,
|
||||
"You don't have permission to update this space."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -25,7 +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';
|
||||
@@ -68,7 +67,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),
|
||||
'user.storage.get': new UserStorageGetQueryHandler(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,20 +0,0 @@
|
||||
export type ChannelUpdateMutationInput = {
|
||||
type: 'channel.update';
|
||||
userId: string;
|
||||
channelId: string;
|
||||
name: string;
|
||||
avatar?: string | null;
|
||||
};
|
||||
|
||||
export type ChannelUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'channel.update': {
|
||||
input: ChannelUpdateMutationInput;
|
||||
output: ChannelUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,20 +0,0 @@
|
||||
export type DatabaseUpdateMutationInput = {
|
||||
type: 'database.update';
|
||||
userId: string;
|
||||
databaseId: string;
|
||||
name: string;
|
||||
avatar?: string | null;
|
||||
};
|
||||
|
||||
export type DatabaseUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'database.update': {
|
||||
input: DatabaseUpdateMutationInput;
|
||||
output: DatabaseUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
export type SelectOptionCreateMutationInput = {
|
||||
type: 'select.option.create';
|
||||
userId: string;
|
||||
databaseId: string;
|
||||
fieldId: string;
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
export type SelectOptionCreateMutationOutput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'select.option.create': {
|
||||
input: SelectOptionCreateMutationInput;
|
||||
output: SelectOptionCreateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export type SelectOptionDeleteMutationInput = {
|
||||
type: 'select.option.delete';
|
||||
userId: string;
|
||||
databaseId: string;
|
||||
fieldId: string;
|
||||
optionId: string;
|
||||
};
|
||||
|
||||
export type SelectOptionDeleteMutationOutput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'select.option.delete': {
|
||||
input: SelectOptionDeleteMutationInput;
|
||||
output: SelectOptionDeleteMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export type SelectOptionUpdateMutationInput = {
|
||||
type: 'select.option.update';
|
||||
userId: string;
|
||||
databaseId: string;
|
||||
fieldId: string;
|
||||
optionId: string;
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
export type SelectOptionUpdateMutationOutput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'select.option.update': {
|
||||
input: SelectOptionUpdateMutationInput;
|
||||
output: SelectOptionUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { DatabaseViewAttributes } from '@colanode/core';
|
||||
|
||||
export type ViewUpdateMutationInput = {
|
||||
type: 'view.update';
|
||||
userId: string;
|
||||
viewId: string;
|
||||
view: DatabaseViewAttributes;
|
||||
};
|
||||
|
||||
export type ViewUpdateMutationOutput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'view.update': {
|
||||
input: ViewUpdateMutationInput;
|
||||
output: ViewUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export type FolderUpdateMutationInput = {
|
||||
type: 'folder.update';
|
||||
userId: string;
|
||||
folderId: string;
|
||||
name: string;
|
||||
avatar?: string | null;
|
||||
};
|
||||
|
||||
export type FolderUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'folder.update': {
|
||||
input: FolderUpdateMutationInput;
|
||||
output: FolderUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -9,22 +9,10 @@ export * from './accounts/google-login';
|
||||
export * from './apps/metadata-delete';
|
||||
export * from './apps/metadata-update';
|
||||
export * from './avatars/avatar-upload';
|
||||
export * from './channels/channel-update';
|
||||
export * from './databases/database-update';
|
||||
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-delete';
|
||||
export * from './databases/select-option-update';
|
||||
export * from './databases/view-name-update';
|
||||
export * from './databases/view-update';
|
||||
export * from './databases/database-name-field-update';
|
||||
export * from './documents/document-update';
|
||||
export * from './files/file-create';
|
||||
export * from './files/file-delete';
|
||||
export * from './files/file-download';
|
||||
export * from './folders/folder-update';
|
||||
export * from './nodes/node-collaborator-create';
|
||||
export * from './nodes/node-collaborator-delete';
|
||||
export * from './nodes/node-collaborator-update';
|
||||
@@ -32,15 +20,8 @@ export * from './nodes/node-interaction-opened';
|
||||
export * from './nodes/node-interaction-seen';
|
||||
export * from './nodes/node-reaction-create';
|
||||
export * from './nodes/node-reaction-delete';
|
||||
export * from './pages/page-update';
|
||||
export * from './records/record-avatar-update';
|
||||
export * from './records/record-field-value-delete';
|
||||
export * from './records/record-field-value-set';
|
||||
export * from './records/record-name-update';
|
||||
export * from './servers/server-create';
|
||||
export * from './servers/server-delete';
|
||||
export * from './spaces/space-update';
|
||||
export * from './spaces/space-child-reorder';
|
||||
export * from './workspaces/workspace-create';
|
||||
export * from './workspaces/workspace-delete';
|
||||
export * from './workspaces/workspace-update';
|
||||
@@ -53,6 +34,7 @@ export * from './apps/tab-update';
|
||||
export * from './apps/tab-delete';
|
||||
export * from './nodes/node-create';
|
||||
export * from './nodes/node-delete';
|
||||
export * from './nodes/node-update';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface MutationMap {}
|
||||
|
||||
21
packages/client/src/mutations/nodes/node-update.ts
Normal file
21
packages/client/src/mutations/nodes/node-update.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { NodeAttributes } from '@colanode/core';
|
||||
|
||||
export type NodeUpdateMutationInput = {
|
||||
type: 'node.update';
|
||||
userId: string;
|
||||
nodeId: string;
|
||||
attributes: Partial<NodeAttributes>;
|
||||
};
|
||||
|
||||
export type NodeUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'node.update': {
|
||||
input: NodeUpdateMutationInput;
|
||||
output: NodeUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export type PageUpdateMutationInput = {
|
||||
type: 'page.update';
|
||||
userId: string;
|
||||
pageId: string;
|
||||
avatar?: string | null;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type PageUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'page.update': {
|
||||
input: PageUpdateMutationInput;
|
||||
output: PageUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export type RecordAvatarUpdateMutationInput = {
|
||||
type: 'record.avatar.update';
|
||||
userId: string;
|
||||
recordId: string;
|
||||
avatar: string;
|
||||
};
|
||||
|
||||
export type RecordAvatarUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'record.avatar.update': {
|
||||
input: RecordAvatarUpdateMutationInput;
|
||||
output: RecordAvatarUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export type RecordFieldValueDeleteMutationInput = {
|
||||
type: 'record.field.value.delete';
|
||||
userId: string;
|
||||
recordId: string;
|
||||
fieldId: string;
|
||||
};
|
||||
|
||||
export type RecordFieldValueDeleteMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'record.field.value.delete': {
|
||||
input: RecordFieldValueDeleteMutationInput;
|
||||
output: RecordFieldValueDeleteMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { FieldValue } from '@colanode/core';
|
||||
|
||||
export type RecordFieldValueSetMutationInput = {
|
||||
type: 'record.field.value.set';
|
||||
userId: string;
|
||||
recordId: string;
|
||||
fieldId: string;
|
||||
value: FieldValue;
|
||||
};
|
||||
|
||||
export type RecordFieldValueSetMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'record.field.value.set': {
|
||||
input: RecordFieldValueSetMutationInput;
|
||||
output: RecordFieldValueSetMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export type RecordNameUpdateMutationInput = {
|
||||
type: 'record.name.update';
|
||||
userId: string;
|
||||
recordId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type RecordNameUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'record.name.update': {
|
||||
input: RecordNameUpdateMutationInput;
|
||||
output: RecordNameUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export type SpaceChildReorderMutationInput = {
|
||||
type: 'space.child.reorder';
|
||||
userId: string;
|
||||
spaceId: string;
|
||||
childId: string;
|
||||
after: string | null;
|
||||
};
|
||||
|
||||
export type SpaceChildReorderMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'space.child.reorder': {
|
||||
input: SpaceChildReorderMutationInput;
|
||||
output: SpaceChildReorderMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
export type SpaceUpdateMutationInput = {
|
||||
type: 'space.update';
|
||||
userId: string;
|
||||
spaceId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
avatar?: string | null;
|
||||
};
|
||||
|
||||
export type SpaceUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/mutations' {
|
||||
interface MutationMap {
|
||||
'space.update': {
|
||||
input: SpaceUpdateMutationInput;
|
||||
output: SpaceUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,7 +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';
|
||||
|
||||
@@ -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,5 +1,3 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalChannelNode } from '@colanode/client/types';
|
||||
import { NodeRole, hasNodeRole } from '@colanode/core';
|
||||
import { ChannelForm } from '@colanode/ui/components/channels/channel-form';
|
||||
@@ -11,7 +9,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@colanode/ui/components/ui/dialog';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database } from '@colanode/ui/data';
|
||||
|
||||
interface ChannelUpdateDialogProps {
|
||||
channel: LocalChannelNode;
|
||||
@@ -27,7 +25,6 @@ export const ChannelUpdateDialog = ({
|
||||
onOpenChange,
|
||||
}: ChannelUpdateDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const { mutate, isPending } = useMutation();
|
||||
const canEdit = hasNodeRole(role, 'editor');
|
||||
|
||||
return (
|
||||
@@ -45,33 +42,28 @@ export const ChannelUpdateDialog = ({
|
||||
name: channel.attributes.name,
|
||||
avatar: channel.attributes.avatar,
|
||||
}}
|
||||
isPending={isPending}
|
||||
isPending={false}
|
||||
submitText="Update"
|
||||
readOnly={!canEdit}
|
||||
onCancel={() => {
|
||||
onOpenChange(false);
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
if (isPending) {
|
||||
const nodes = database.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(channel.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'channel.update',
|
||||
channelId: channel.id,
|
||||
name: values.name,
|
||||
avatar: values.avatar,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess() {
|
||||
onOpenChange(false);
|
||||
toast.success('Channel updated');
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
nodes.update(channel.id, (draft) => {
|
||||
if (draft.attributes.type !== 'channel') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = values.name;
|
||||
draft.attributes.avatar = values.avatar;
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { eq, useLiveQuery as useLiveQueryTanstack } from '@tanstack/react-db';
|
||||
import { CircleAlert, CircleDashed } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import {
|
||||
CollaboratorFieldAttributes,
|
||||
@@ -13,7 +12,7 @@ import { BoardViewContext } from '@colanode/ui/contexts/board-view';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database } from '@colanode/ui/data';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface BoardViewColumnsCollaboratorProps {
|
||||
@@ -81,17 +80,19 @@ export const BoardViewColumnsCollaborator = ({
|
||||
),
|
||||
canDrag: (record) => record.canEdit,
|
||||
onDragEnd: async (record, value) => {
|
||||
if (!value) {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.delete',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
if (!value) {
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
delete draft.attributes.fields[field.id];
|
||||
});
|
||||
} else {
|
||||
if (value.type !== 'string_array') {
|
||||
return;
|
||||
@@ -114,17 +115,13 @@ export const BoardViewColumnsCollaborator = ({
|
||||
};
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.set',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
value: newValue,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
draft.attributes.fields[field.id] = newValue;
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
@@ -150,29 +147,27 @@ export const BoardViewColumnsCollaborator = ({
|
||||
),
|
||||
canDrag: () => true,
|
||||
onDragEnd: async (record, value) => {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.delete',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
delete draft.attributes.fields[field.id];
|
||||
});
|
||||
} else {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.set',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
value,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
draft.attributes.fields[field.id] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
@@ -198,7 +193,7 @@ const BoardViewColumnCollaboratorHeader = ({
|
||||
|
||||
const userQuery = useLiveQueryTanstack((q) =>
|
||||
q
|
||||
.from({ users: database.workspace(workspace.userId).users })
|
||||
.from({ users: appDatabase.workspace(workspace.userId).users })
|
||||
.where(({ users }) => eq(users.id, collaborator))
|
||||
.select(({ users }) => ({
|
||||
id: users.id,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { CircleDashed } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import {
|
||||
DatabaseViewFilterAttributes,
|
||||
@@ -13,6 +12,7 @@ import { BoardViewContext } from '@colanode/ui/contexts/board-view';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
import { getSelectOptionLightColorClass } from '@colanode/ui/lib/databases';
|
||||
|
||||
@@ -90,17 +90,19 @@ export const BoardViewColumnsMultiSelect = ({
|
||||
),
|
||||
canDrag: (record) => record.canEdit,
|
||||
onDragEnd: async (record, value) => {
|
||||
if (!value) {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.delete',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
if (!value) {
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
delete draft.attributes.fields[field.id];
|
||||
});
|
||||
} else {
|
||||
if (value.type !== 'string_array') {
|
||||
return;
|
||||
@@ -122,17 +124,13 @@ export const BoardViewColumnsMultiSelect = ({
|
||||
};
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.set',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
value: newValue,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
draft.attributes.fields[field.id] = newValue;
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
@@ -159,29 +157,27 @@ export const BoardViewColumnsMultiSelect = ({
|
||||
dragOverClass: noValueDraggingClass,
|
||||
canDrag: () => true,
|
||||
onDragEnd: async (record, value) => {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.delete',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
delete draft.attributes.fields[field.id];
|
||||
});
|
||||
} else {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.set',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
value,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
draft.attributes.fields[field.id] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { CircleDashed } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import {
|
||||
DatabaseViewFilterAttributes,
|
||||
@@ -12,6 +11,7 @@ import { BoardViewContext } from '@colanode/ui/contexts/board-view';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
import { getSelectOptionLightColorClass } from '@colanode/ui/lib/databases';
|
||||
|
||||
@@ -89,29 +89,27 @@ export const BoardViewColumnsSelect = ({
|
||||
),
|
||||
canDrag: (record) => record.canEdit,
|
||||
onDragEnd: async (record, value) => {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.delete',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
delete draft.attributes.fields[field.id];
|
||||
});
|
||||
} else {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.set',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
value,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
draft.attributes.fields[field.id] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
@@ -138,29 +136,27 @@ export const BoardViewColumnsSelect = ({
|
||||
dragOverClass: noValueDraggingClass,
|
||||
canDrag: () => true,
|
||||
onDragEnd: async (record, value) => {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.delete',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
delete draft.attributes.fields[field.id];
|
||||
});
|
||||
} else {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.set',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
value,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
draft.attributes.fields[field.id] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalDatabaseNode } from '@colanode/client/types';
|
||||
import { NodeRole, hasNodeRole } from '@colanode/core';
|
||||
import { DatabaseForm } from '@colanode/ui/components/databases/database-form';
|
||||
@@ -11,7 +9,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@colanode/ui/components/ui/dialog';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
interface DatabaseUpdateDialogProps {
|
||||
database: LocalDatabaseNode;
|
||||
@@ -27,7 +25,7 @@ export const DatabaseUpdateDialog = ({
|
||||
onOpenChange,
|
||||
}: DatabaseUpdateDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
const canEdit = hasNodeRole(role, 'editor');
|
||||
|
||||
return (
|
||||
@@ -45,33 +43,28 @@ export const DatabaseUpdateDialog = ({
|
||||
name: database.attributes.name,
|
||||
avatar: database.attributes.avatar,
|
||||
}}
|
||||
isPending={isPending}
|
||||
isPending={false}
|
||||
submitText="Update"
|
||||
readOnly={!canEdit}
|
||||
onCancel={() => {
|
||||
onOpenChange(false);
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
if (isPending) {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(database.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'database.update',
|
||||
databaseId: database.id,
|
||||
name: values.name,
|
||||
avatar: values.avatar,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess() {
|
||||
onOpenChange(false);
|
||||
toast.success('Database updated');
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = values.name;
|
||||
draft.attributes.avatar = values.avatar;
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalDatabaseNode } from '@colanode/client/types';
|
||||
import { NodeRole, hasNodeRole } from '@colanode/core';
|
||||
import { DatabaseContext } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
|
||||
interface DatabaseProps {
|
||||
database: LocalDatabaseNode;
|
||||
@@ -13,7 +11,6 @@ interface DatabaseProps {
|
||||
}
|
||||
|
||||
export const Database = ({ database, role, children }: DatabaseProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const canEdit = hasNodeRole(role, 'editor');
|
||||
const canCreateRecord = hasNodeRole(role, 'editor');
|
||||
|
||||
@@ -27,112 +24,6 @@ export const Database = ({ database, role, children }: DatabaseProps) => {
|
||||
fields: Object.values(database.attributes.fields),
|
||||
canEdit,
|
||||
canCreateRecord,
|
||||
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) => {
|
||||
if (!canEdit) return;
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'select.option.create',
|
||||
databaseId: database.id,
|
||||
fieldId,
|
||||
name,
|
||||
color,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
},
|
||||
updateSelectOption: async (fieldId, attributes) => {
|
||||
if (!canEdit) return;
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'select.option.update',
|
||||
databaseId: database.id,
|
||||
fieldId,
|
||||
optionId: attributes.id,
|
||||
name: attributes.name,
|
||||
color: attributes.color,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
},
|
||||
deleteSelectOption: async (fieldId, optionId) => {
|
||||
if (!canEdit) return;
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'select.option.delete',
|
||||
databaseId: database.id,
|
||||
fieldId,
|
||||
optionId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
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 { DatabaseSelect } from '@colanode/ui/components/databases/database-select';
|
||||
import { FieldTypeSelect } from '@colanode/ui/components/databases/fields/field-type-select';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
@@ -22,10 +31,9 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@colanode/ui/components/ui/popover';
|
||||
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 { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, { message: 'Name is required' }),
|
||||
@@ -50,6 +58,8 @@ const formSchema = z.object({
|
||||
relationDatabaseId: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
type FieldCreateFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
interface FieldCreatePopoverProps {
|
||||
button: React.ReactNode;
|
||||
onSuccess?: (fieldId: string) => void;
|
||||
@@ -65,8 +75,6 @@ export const FieldCreatePopover = ({
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
@@ -75,6 +83,72 @@ export const FieldCreatePopover = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: async (values: FieldCreateFormValues) => {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
|
||||
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.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxIndex = Object.values(draft.attributes.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,
|
||||
index,
|
||||
};
|
||||
|
||||
if (newField.type === 'relation') {
|
||||
newField.databaseId = values.relationDatabaseId;
|
||||
}
|
||||
|
||||
draft.attributes.fields[fieldId] = newField;
|
||||
});
|
||||
|
||||
return fieldId;
|
||||
},
|
||||
onSuccess: (fieldId) => {
|
||||
form.reset();
|
||||
setOpen(false);
|
||||
|
||||
if (fieldId) {
|
||||
onSuccess?.(fieldId);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message as string);
|
||||
},
|
||||
});
|
||||
|
||||
const type = form.watch('type');
|
||||
|
||||
const handleCancelClick = () => {
|
||||
@@ -82,27 +156,6 @@ export const FieldCreatePopover = ({
|
||||
form.reset();
|
||||
};
|
||||
|
||||
const handleSubmit = (values: z.infer<typeof formSchema>) => {
|
||||
mutate({
|
||||
input: {
|
||||
type: 'field.create',
|
||||
databaseId: database.id,
|
||||
name: values.name,
|
||||
fieldType: values.type,
|
||||
userId: workspace.userId,
|
||||
relationDatabaseId: values.relationDatabaseId,
|
||||
},
|
||||
onSuccess: (output) => {
|
||||
setOpen(false);
|
||||
form.reset();
|
||||
onSuccess?.(output.id);
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (!database.canEdit) {
|
||||
return null;
|
||||
}
|
||||
@@ -114,7 +167,7 @@ export const FieldCreatePopover = ({
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex flex-col gap-2"
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
onSubmit={form.handleSubmit((values) => mutate(values))}
|
||||
>
|
||||
<div className="flex-grow space-y-4 py-2 pb-4">
|
||||
<FormField
|
||||
@@ -170,11 +223,11 @@ export const FieldCreatePopover = ({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleCancelClick}
|
||||
disabled={isPending}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" size="sm" disabled={isPending}>
|
||||
{isPending && <Spinner className="mr-1" />}
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
} from '@colanode/ui/components/ui/alert-dialog';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
interface FieldDeleteDialogProps {
|
||||
id: string;
|
||||
@@ -21,6 +23,7 @@ export const FieldDeleteDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: FieldDeleteDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
|
||||
return (
|
||||
@@ -39,8 +42,22 @@ export const FieldDeleteDialog = ({
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={async () => {
|
||||
database.deleteField(id);
|
||||
onClick={() => {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(database.id)) {
|
||||
console.error('Database not found');
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
console.error('Database not found');
|
||||
return;
|
||||
}
|
||||
|
||||
delete draft.attributes.fields[id];
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { FieldAttributes } from '@colanode/core';
|
||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
interface FieldRenameInputProps {
|
||||
field: FieldAttributes;
|
||||
}
|
||||
|
||||
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
|
||||
return (
|
||||
@@ -16,7 +19,23 @@ export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
||||
readOnly={!database.canEdit}
|
||||
onChange={(newName) => {
|
||||
if (newName === field.name) return;
|
||||
database.renameField(field.id, newName);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(database.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldDraft = draft.attributes.fields[field.id];
|
||||
if (!fieldDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldDraft.name = newName;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { Check, Plus, X } from 'lucide-react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
|
||||
import {
|
||||
compareString,
|
||||
generateFractionalIndex,
|
||||
generateId,
|
||||
IdType,
|
||||
MultiSelectFieldAttributes,
|
||||
SelectFieldAttributes,
|
||||
} from '@colanode/core';
|
||||
@@ -18,7 +24,7 @@ import {
|
||||
} from '@colanode/ui/components/ui/command';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
import { getRandomSelectOptionColor } from '@colanode/ui/lib/databases';
|
||||
|
||||
interface SelectFieldOptionsProps {
|
||||
@@ -36,7 +42,6 @@ export const SelectFieldOptions = ({
|
||||
}: SelectFieldOptionsProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
const selectOptions = Object.values(field.options ?? {});
|
||||
|
||||
@@ -47,6 +52,71 @@ export const SelectFieldOptions = ({
|
||||
allowAdd &&
|
||||
!selectOptions.some((option) => option.name === inputValue.trim());
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: async ({ name, color }: { name: string; color: string }) => {
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(database.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectOptionId = generateId(IdType.SelectOption);
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldDraft = draft.attributes.fields[field.id];
|
||||
if (!fieldDraft) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FieldNotFound,
|
||||
'The field you are trying to create a select option in does not exist.'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
fieldDraft.type !== 'multi_select' &&
|
||||
fieldDraft.type !== 'select'
|
||||
) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FieldTypeInvalid,
|
||||
'The field you are trying to create a select option in is not a "Select" or "Multi-Select" field.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!fieldDraft.options) {
|
||||
fieldDraft.options = {};
|
||||
}
|
||||
|
||||
const maxIndex = Object.values(fieldDraft.options)
|
||||
.map((selectOption) => selectOption.index)
|
||||
.sort((a, b) => -compareString(a, b))[0];
|
||||
|
||||
const index = generateFractionalIndex(maxIndex, null);
|
||||
|
||||
fieldDraft.options[selectOptionId] = {
|
||||
name: name,
|
||||
id: selectOptionId,
|
||||
color: color,
|
||||
index: index,
|
||||
};
|
||||
});
|
||||
|
||||
return selectOptionId;
|
||||
},
|
||||
onSuccess: (selectOptionId) => {
|
||||
if (!selectOptionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue('');
|
||||
setColor(getRandomSelectOptionColor());
|
||||
onSelect(selectOptionId);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message as string);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Command className="min-h-min">
|
||||
<CommandInput
|
||||
@@ -104,33 +174,9 @@ export const SelectFieldOptions = ({
|
||||
key={inputValue.trim()}
|
||||
value={inputValue.trim()}
|
||||
onSelect={() => {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputValue.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'select.option.create',
|
||||
databaseId: database.id,
|
||||
fieldId: field.id,
|
||||
name: inputValue.trim(),
|
||||
color,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess(output) {
|
||||
setInputValue('');
|
||||
setColor(getRandomSelectOptionColor());
|
||||
onSelect(output.id);
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
mutate({ name: inputValue.trim(), color });
|
||||
}}
|
||||
disabled={isPending}
|
||||
className="flex flex-row items-center gap-2"
|
||||
>
|
||||
<span className="text-xs text-muted-foreground">Create</span>
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
} from '@colanode/ui/components/ui/alert-dialog';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
interface SelectOptionDeleteDialogProps {
|
||||
fieldId: string;
|
||||
@@ -23,6 +25,7 @@ export const SelectOptionDeleteDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: SelectOptionDeleteDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
|
||||
return (
|
||||
@@ -42,7 +45,36 @@ export const SelectOptionDeleteDialog = ({
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
database.deleteSelectOption(fieldId, optionId);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(database.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldDraft = draft.attributes.fields[fieldId];
|
||||
if (!fieldDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldDraft.type !== 'select' &&
|
||||
fieldDraft.type !== 'multi_select'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fieldDraft.options) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete fieldDraft.options[optionId];
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
import { selectOptionColors } from '@colanode/ui/lib/databases';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
|
||||
@@ -25,6 +27,7 @@ export const SelectOptionSettingsPopover = ({
|
||||
option,
|
||||
}: SelectOptionSettingsPopoverProps) => {
|
||||
const database = useDatabase();
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const [openSetttingsPopover, setOpenSetttingsPopover] = useState(false);
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||
@@ -46,9 +49,38 @@ export const SelectOptionSettingsPopover = ({
|
||||
onChange={(newName) => {
|
||||
if (newName === option.name) return;
|
||||
|
||||
database.updateSelectOption(fieldId, {
|
||||
...option,
|
||||
name: newName,
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(database.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldDraft = draft.attributes.fields[fieldId];
|
||||
if (!fieldDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldDraft.type !== 'select' &&
|
||||
fieldDraft.type !== 'multi_select'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fieldDraft.options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const optionDraft = fieldDraft.options[option.id];
|
||||
if (!optionDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
optionDraft.name = newName;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -61,9 +93,38 @@ export const SelectOptionSettingsPopover = ({
|
||||
key={color.value}
|
||||
className="flex cursor-pointer flex-row items-center gap-2 rounded-md p-1 hover:bg-accent"
|
||||
onClick={() => {
|
||||
database.updateSelectOption(fieldId, {
|
||||
...option,
|
||||
color: color.value,
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(database.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldDraft = draft.attributes.fields[fieldId];
|
||||
if (!fieldDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldDraft.type !== 'select' &&
|
||||
fieldDraft.type !== 'multi_select'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fieldDraft.options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const optionDraft = fieldDraft.options[option.id];
|
||||
if (!optionDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
optionDraft.color = color.value;
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import isHotkey from 'is-hotkey';
|
||||
import { SquareArrowOutUpRight } from 'lucide-react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { RecordNode } from '@colanode/core';
|
||||
import { Link } from '@colanode/ui/components/ui/link';
|
||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
interface NameEditorProps {
|
||||
initialValue: string;
|
||||
@@ -61,26 +59,23 @@ export const TableViewNameCell = ({ record }: TableViewNameCellProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const [isEditing, setIsEditing] = React.useState(false);
|
||||
|
||||
const { mutate, isPending } = useMutation();
|
||||
const canEdit = true;
|
||||
const hasName = record.attributes.name && record.attributes.name.length > 0;
|
||||
|
||||
const handleSave = (newName: string) => {
|
||||
if (newName === record.attributes.name) return;
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'record.name.update',
|
||||
name: newName,
|
||||
recordId: record.id,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess() {
|
||||
setIsEditing(false);
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = newName;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -112,11 +107,6 @@ export const TableViewNameCell = ({ record }: TableViewNameCellProps) => {
|
||||
>
|
||||
<SquareArrowOutUpRight className="mr-1 size-4" /> <p>Open</p>
|
||||
</Link>
|
||||
{isPending && (
|
||||
<span className="absolute right-2 text-muted-foreground">
|
||||
<Spinner size="small" />
|
||||
</span>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -13,9 +13,12 @@ import { Separator } from '@colanode/ui/components/ui/separator';
|
||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
|
||||
export const TableViewNameHeader = () => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
const view = useDatabaseView();
|
||||
|
||||
@@ -90,7 +93,18 @@ export const TableViewNameHeader = () => {
|
||||
readOnly={!database.canEdit}
|
||||
onChange={(newName) => {
|
||||
if (newName === database.nameField?.name) return;
|
||||
database.updateNameField(newName);
|
||||
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.nameField = {
|
||||
...draft.attributes.nameField,
|
||||
name: newName,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import {
|
||||
@@ -80,36 +79,34 @@ export const View = ({ view }: ViewProps) => {
|
||||
rename: async (name: string) => {
|
||||
if (!database.canEdit) return;
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.name = name;
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = name;
|
||||
});
|
||||
},
|
||||
updateAvatar: async (avatar: string) => {
|
||||
if (!database.canEdit) return;
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.avatar = avatar;
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.avatar = avatar;
|
||||
});
|
||||
},
|
||||
setFieldDisplay: async (id: string, display: boolean) => {
|
||||
if (!database.canEdit) return;
|
||||
@@ -117,27 +114,26 @@ export const View = ({ view }: ViewProps) => {
|
||||
const viewField = view.attributes.fields?.[id];
|
||||
if (viewField && viewField.display === display) return;
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.fields = viewAttributes.fields ?? {};
|
||||
if (!viewAttributes.fields[id]) {
|
||||
viewAttributes.fields[id] = {
|
||||
id: id,
|
||||
display: display,
|
||||
};
|
||||
} else {
|
||||
viewAttributes.fields[id].display = display;
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.fields = draft.attributes.fields ?? {};
|
||||
if (!draft.attributes.fields[id]) {
|
||||
draft.attributes.fields[id] = {
|
||||
id: id,
|
||||
display: display,
|
||||
};
|
||||
} else {
|
||||
draft.attributes.fields[id].display = display;
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
},
|
||||
resizeField: async (id: string, width: number) => {
|
||||
if (!database.canEdit) {
|
||||
@@ -149,27 +145,26 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.fields = viewAttributes.fields ?? {};
|
||||
if (!viewAttributes.fields[id]) {
|
||||
viewAttributes.fields[id] = {
|
||||
id: id,
|
||||
width: width,
|
||||
};
|
||||
} else {
|
||||
viewAttributes.fields[id].width = width;
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.fields = draft.attributes.fields ?? {};
|
||||
if (!draft.attributes.fields[id]) {
|
||||
draft.attributes.fields[id] = {
|
||||
id: id,
|
||||
width: width,
|
||||
};
|
||||
} else {
|
||||
draft.attributes.fields[id].width = width;
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
},
|
||||
resizeName: async (width: number) => {
|
||||
if (!database.canEdit) {
|
||||
@@ -180,38 +175,36 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.nameWidth = width;
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.nameWidth = width;
|
||||
});
|
||||
},
|
||||
setGroupBy: async (fieldId: string | null) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.groupBy = fieldId;
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.groupBy = fieldId;
|
||||
});
|
||||
},
|
||||
moveField: async (id: string, after: string) => {
|
||||
if (!database.canEdit) {
|
||||
@@ -224,31 +217,31 @@ export const View = ({ view }: ViewProps) => {
|
||||
id,
|
||||
after
|
||||
);
|
||||
|
||||
if (newIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.fields = viewAttributes.fields ?? {};
|
||||
if (!viewAttributes.fields[id]) {
|
||||
viewAttributes.fields[id] = {
|
||||
id: id,
|
||||
index: newIndex,
|
||||
};
|
||||
} else {
|
||||
viewAttributes.fields[id].index = newIndex;
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.fields = draft.attributes.fields ?? {};
|
||||
if (!draft.attributes.fields[id]) {
|
||||
draft.attributes.fields[id] = {
|
||||
id: id,
|
||||
index: newIndex,
|
||||
};
|
||||
} else {
|
||||
draft.attributes.fields[id].index = newIndex;
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
},
|
||||
isFieldFilterOpened: (fieldId: string) =>
|
||||
openedFieldFilters.includes(fieldId),
|
||||
@@ -262,48 +255,47 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.filters = viewAttributes.filters ?? {};
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldId === SpecialId.Name) {
|
||||
const operators = getFieldFilterOperators('text');
|
||||
const filter: DatabaseViewFieldFilterAttributes = {
|
||||
type: 'field',
|
||||
id: fieldId,
|
||||
fieldId,
|
||||
operator: operators[0]?.value ?? 'contains',
|
||||
};
|
||||
|
||||
viewAttributes.filters[fieldId] = filter;
|
||||
} else {
|
||||
const field = database.fields.find((f) => f.id === fieldId);
|
||||
if (!field) {
|
||||
const filterId = generateId(IdType.ViewFilter);
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
const operators = getFieldFilterOperators(field.type);
|
||||
const filter: DatabaseViewFieldFilterAttributes = {
|
||||
type: 'field',
|
||||
id: fieldId,
|
||||
fieldId,
|
||||
operator: operators[0]?.value ?? '',
|
||||
};
|
||||
draft.attributes.filters = draft.attributes.filters ?? {};
|
||||
if (fieldId === SpecialId.Name) {
|
||||
const operators = getFieldFilterOperators('text');
|
||||
const filter: DatabaseViewFieldFilterAttributes = {
|
||||
type: 'field',
|
||||
id: filterId,
|
||||
fieldId,
|
||||
operator: operators[0]?.value ?? 'contains',
|
||||
};
|
||||
|
||||
viewAttributes.filters[fieldId] = filter;
|
||||
}
|
||||
draft.attributes.filters[filterId] = filter;
|
||||
} else {
|
||||
const field = database.fields.find((f) => f.id === fieldId);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
const operators = getFieldFilterOperators(field.type);
|
||||
const filter: DatabaseViewFieldFilterAttributes = {
|
||||
type: 'field',
|
||||
id: filterId,
|
||||
fieldId,
|
||||
operator: operators[0]?.value ?? '',
|
||||
};
|
||||
|
||||
draft.attributes.filters[filterId] = filter;
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
} else {
|
||||
setOpenedFieldFilters((prev) => [...prev, fieldId]);
|
||||
}
|
||||
setOpenedFieldFilters((prev) => [...prev, filterId]);
|
||||
},
|
||||
updateFilter: async (
|
||||
id: string,
|
||||
@@ -317,22 +309,22 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.filters = viewAttributes.filters ?? {};
|
||||
viewAttributes.filters[id] = filter;
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
const filters = draft.attributes.filters ?? {};
|
||||
filters[id] = filter;
|
||||
draft.attributes.filters = filters;
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
} else {
|
||||
setIsSearchBarOpened(true);
|
||||
}
|
||||
setIsSearchBarOpened(true);
|
||||
},
|
||||
removeFilter: async (id: string) => {
|
||||
if (!database.canEdit) {
|
||||
@@ -343,22 +335,28 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.filters = viewAttributes.filters ?? {};
|
||||
delete viewAttributes.filters[id];
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!draft.attributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!draft.attributes.filters?.[id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete draft.attributes.filters[id];
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
} else {
|
||||
setIsSearchBarOpened(true);
|
||||
}
|
||||
setIsSearchBarOpened(true);
|
||||
},
|
||||
initFieldSort: async (fieldId: string, direction: SortDirection) => {
|
||||
if (!database.canEdit) {
|
||||
@@ -370,45 +368,41 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.sorts = viewAttributes.sorts ?? {};
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldId === SpecialId.Name) {
|
||||
const sort: DatabaseViewSortAttributes = {
|
||||
id: fieldId,
|
||||
fieldId,
|
||||
direction,
|
||||
};
|
||||
|
||||
viewAttributes.sorts[fieldId] = sort;
|
||||
} else {
|
||||
const field = database.fields.find((f) => f.id === fieldId);
|
||||
if (!field) {
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
|
||||
const sort: DatabaseViewSortAttributes = {
|
||||
id: fieldId,
|
||||
fieldId,
|
||||
direction,
|
||||
};
|
||||
draft.attributes.sorts = draft.attributes.sorts ?? {};
|
||||
|
||||
viewAttributes.sorts[fieldId] = sort;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
if (fieldId === SpecialId.Name) {
|
||||
const sort: DatabaseViewSortAttributes = {
|
||||
id: fieldId,
|
||||
fieldId,
|
||||
direction,
|
||||
};
|
||||
draft.attributes.sorts[fieldId] = sort;
|
||||
} else {
|
||||
const field = database.fields.find((f) => f.id === fieldId);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
const sort: DatabaseViewSortAttributes = {
|
||||
id: fieldId,
|
||||
fieldId,
|
||||
direction,
|
||||
};
|
||||
draft.attributes.sorts[fieldId] = sort;
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
} else {
|
||||
setIsSearchBarOpened(true);
|
||||
setIsSortsOpened(true);
|
||||
}
|
||||
setIsSearchBarOpened(true);
|
||||
setIsSortsOpened(true);
|
||||
},
|
||||
updateSort: async (id: string, sort: DatabaseViewSortAttributes) => {
|
||||
if (!database.canEdit) {
|
||||
@@ -419,23 +413,21 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.sorts = viewAttributes.sorts ?? {};
|
||||
viewAttributes.sorts[id] = sort;
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
draft.attributes.sorts = draft.attributes.sorts ?? {};
|
||||
draft.attributes.sorts[id] = sort;
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
} else {
|
||||
setIsSearchBarOpened(true);
|
||||
setIsSortsOpened(true);
|
||||
}
|
||||
setIsSearchBarOpened(true);
|
||||
setIsSortsOpened(true);
|
||||
},
|
||||
removeSort: async (id: string) => {
|
||||
if (!database.canEdit) {
|
||||
@@ -446,23 +438,21 @@ export const View = ({ view }: ViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewAttributes = { ...view.attributes };
|
||||
viewAttributes.sorts = viewAttributes.sorts ?? {};
|
||||
delete viewAttributes.sorts[id];
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'view.update',
|
||||
userId: workspace.userId,
|
||||
viewId: view.id,
|
||||
view: viewAttributes,
|
||||
nodes.update(view.id, (draft) => {
|
||||
if (draft.attributes.type !== 'database_view') {
|
||||
return;
|
||||
}
|
||||
draft.attributes.sorts = draft.attributes.sorts ?? {};
|
||||
delete draft.attributes.sorts[id];
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
} else {
|
||||
setIsSearchBarOpened(true);
|
||||
setIsSortsOpened(true);
|
||||
}
|
||||
setIsSearchBarOpened(true);
|
||||
setIsSortsOpened(true);
|
||||
},
|
||||
openSearchBar: () => {
|
||||
setIsSearchBarOpened(true);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalFolderNode } from '@colanode/client/types';
|
||||
import { NodeRole, hasNodeRole } from '@colanode/core';
|
||||
import { FolderForm } from '@colanode/ui/components/folders/folder-form';
|
||||
@@ -11,7 +9,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@colanode/ui/components/ui/dialog';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database } from '@colanode/ui/data';
|
||||
|
||||
interface FolderUpdateDialogProps {
|
||||
folder: LocalFolderNode;
|
||||
@@ -27,7 +25,6 @@ export const FolderUpdateDialog = ({
|
||||
onOpenChange,
|
||||
}: FolderUpdateDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const { mutate, isPending } = useMutation();
|
||||
const canEdit = hasNodeRole(role, 'editor');
|
||||
|
||||
return (
|
||||
@@ -43,33 +40,28 @@ export const FolderUpdateDialog = ({
|
||||
name: folder.attributes.name,
|
||||
avatar: folder.attributes.avatar,
|
||||
}}
|
||||
isPending={isPending}
|
||||
isPending={false}
|
||||
submitText="Update"
|
||||
readOnly={!canEdit}
|
||||
onCancel={() => {
|
||||
onOpenChange(false);
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
if (isPending) {
|
||||
const nodes = database.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(folder.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'folder.update',
|
||||
folderId: folder.id,
|
||||
name: values.name,
|
||||
avatar: values.avatar,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess() {
|
||||
onOpenChange(false);
|
||||
toast.success('Folder was updated successfully');
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
nodes.update(folder.id, (draft) => {
|
||||
if (draft.attributes.type !== 'folder') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = values.name;
|
||||
draft.attributes.avatar = values.avatar;
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalPageNode } from '@colanode/client/types';
|
||||
import { NodeRole, hasNodeRole } from '@colanode/core';
|
||||
import { PageForm } from '@colanode/ui/components/pages/page-form';
|
||||
@@ -11,7 +9,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@colanode/ui/components/ui/dialog';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database } from '@colanode/ui/data';
|
||||
|
||||
interface PageUpdateDialogProps {
|
||||
page: LocalPageNode;
|
||||
@@ -27,7 +25,6 @@ export const PageUpdateDialog = ({
|
||||
onOpenChange,
|
||||
}: PageUpdateDialogProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const { mutate, isPending } = useMutation();
|
||||
const canEdit = hasNodeRole(role, 'editor');
|
||||
|
||||
return (
|
||||
@@ -43,31 +40,26 @@ export const PageUpdateDialog = ({
|
||||
name: page.attributes.name,
|
||||
avatar: page.attributes.avatar,
|
||||
}}
|
||||
isPending={isPending}
|
||||
isPending={false}
|
||||
submitText="Update"
|
||||
readOnly={!canEdit}
|
||||
onCancel={() => onOpenChange(false)}
|
||||
onSubmit={(values) => {
|
||||
if (isPending) {
|
||||
const nodes = database.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(page.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'page.update',
|
||||
pageId: page.id,
|
||||
name: values.name,
|
||||
avatar: values.avatar,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onSuccess() {
|
||||
onOpenChange(false);
|
||||
toast.success('Page was updated successfully');
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
nodes.update(page.id, (draft) => {
|
||||
if (draft.attributes.type !== 'page') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = values.name;
|
||||
draft.attributes.avatar = values.avatar;
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
|
||||
import { Button } from '@colanode/ui/components/ui/button';
|
||||
import { useRecord } from '@colanode/ui/contexts/record';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
export const RecordAvatar = () => {
|
||||
const workspace = useWorkspace();
|
||||
const record = useRecord();
|
||||
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
if (!record.canEdit) {
|
||||
return (
|
||||
<Button type="button" variant="outline" size="icon">
|
||||
@@ -29,19 +25,19 @@ export const RecordAvatar = () => {
|
||||
return (
|
||||
<AvatarPopover
|
||||
onPick={(avatar) => {
|
||||
if (isPending) return;
|
||||
if (avatar === record.avatar) return;
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'record.avatar.update',
|
||||
recordId: record.id,
|
||||
avatar,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.avatar = avatar;
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
||||
import { useRecord } from '@colanode/ui/contexts/record';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
export const RecordName = () => {
|
||||
const workspace = useWorkspace();
|
||||
const record = useRecord();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -29,24 +27,21 @@ export const RecordName = () => {
|
||||
readOnly={!record.canEdit}
|
||||
ref={inputRef}
|
||||
onChange={(value) => {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === record.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'record.name.update',
|
||||
recordId: record.id,
|
||||
name: value,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = value;
|
||||
});
|
||||
}}
|
||||
className="font-heading border-b border-none pl-1 text-4xl font-bold shadow-none focus-visible:ring-0"
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalRecordNode } from '@colanode/client/types';
|
||||
import { NodeRole, hasNodeRole } from '@colanode/core';
|
||||
import { RecordContext } from '@colanode/ui/contexts/record';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { database as appDatabase } from '@colanode/ui/data';
|
||||
|
||||
export const RecordProvider = ({
|
||||
record,
|
||||
@@ -34,29 +33,32 @@ export const RecordProvider = ({
|
||||
localRevision: record.localRevision,
|
||||
canEdit,
|
||||
updateFieldValue: async (field, value) => {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.set',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
value,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.fields[field.id] = value;
|
||||
});
|
||||
},
|
||||
removeFieldValue: async (field) => {
|
||||
const result = await window.colanode.executeMutation({
|
||||
type: 'record.field.value.delete',
|
||||
recordId: record.id,
|
||||
fieldId: field.id,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error.message);
|
||||
const nodes = appDatabase.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(record.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(record.id, (draft) => {
|
||||
if (draft.attributes.type !== 'record') {
|
||||
return;
|
||||
}
|
||||
|
||||
delete draft.attributes.fields[field.id];
|
||||
});
|
||||
},
|
||||
getBooleanValue: (field) => {
|
||||
const fieldValue = record.attributes.fields[field.id];
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalSpaceNode } from '@colanode/client/types';
|
||||
import { NodeRole, hasNodeRole } from '@colanode/core';
|
||||
import { NodeCollaborators } from '@colanode/ui/components/collaborators/node-collaborators';
|
||||
@@ -7,7 +5,7 @@ import { SpaceDelete } from '@colanode/ui/components/spaces/space-delete';
|
||||
import { SpaceForm } from '@colanode/ui/components/spaces/space-form';
|
||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
import { database } from '@colanode/ui/data';
|
||||
|
||||
interface SpaceBodyProps {
|
||||
space: LocalSpaceNode;
|
||||
@@ -16,7 +14,6 @@ interface SpaceBodyProps {
|
||||
|
||||
export const SpaceBody = ({ space, role }: SpaceBodyProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
const canEdit = hasNodeRole(role, 'admin');
|
||||
const canDelete = hasNodeRole(role, 'admin');
|
||||
@@ -36,24 +33,22 @@ export const SpaceBody = ({ space, role }: SpaceBodyProps) => {
|
||||
}}
|
||||
readOnly={!canEdit}
|
||||
onSubmit={(values) => {
|
||||
mutate({
|
||||
input: {
|
||||
type: 'space.update',
|
||||
userId: workspace.userId,
|
||||
spaceId: space.id,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
avatar: values.avatar,
|
||||
},
|
||||
onSuccess() {
|
||||
toast.success('Space updated');
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
const nodes = database.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(space.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.update(space.id, (draft) => {
|
||||
if (draft.attributes.type !== 'space') {
|
||||
return;
|
||||
}
|
||||
|
||||
draft.attributes.name = values.name;
|
||||
draft.attributes.description = values.description;
|
||||
draft.attributes.avatar = values.avatar;
|
||||
});
|
||||
}}
|
||||
isPending={isPending}
|
||||
isPending={false}
|
||||
saveText="Update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { eq, inArray, useLiveQuery } from '@tanstack/react-db';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { RefAttributes, useRef } from 'react';
|
||||
import { useDrop } from 'react-dnd';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { LocalSpaceNode } from '@colanode/client/types';
|
||||
import { LocalNode, LocalSpaceNode } from '@colanode/client/types';
|
||||
import { extractNodeRole } from '@colanode/core';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { SpaceSidebarDropdown } from '@colanode/ui/components/spaces/space-sidebar-dropdown';
|
||||
@@ -15,9 +15,11 @@ import {
|
||||
import { Link } from '@colanode/ui/components/ui/link';
|
||||
import { WorkspaceSidebarItem } from '@colanode/ui/components/workspaces/sidebars/sidebar-item';
|
||||
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 { database } from '@colanode/ui/data';
|
||||
import {
|
||||
generateSpaceChildIndex,
|
||||
sortSpaceChildren,
|
||||
} from '@colanode/ui/lib/spaces';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
|
||||
interface SpaceSidebarItemProps {
|
||||
@@ -26,17 +28,18 @@ interface SpaceSidebarItemProps {
|
||||
|
||||
export const SpaceSidebarItem = ({ space }: SpaceSidebarItemProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const mutation = useMutation();
|
||||
|
||||
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: database.workspace(workspace.userId).nodes })
|
||||
.where(({ nodes }) => eq(nodes.parentId, space.id))
|
||||
.where(({ nodes }) =>
|
||||
inArray(nodes.type, ['page', 'channel', 'database', 'folder'])
|
||||
)
|
||||
);
|
||||
|
||||
const [dropMonitor, dropRef] = useDrop({
|
||||
accept: 'sidebar-item',
|
||||
@@ -55,17 +58,33 @@ export const SpaceSidebarItem = ({ space }: SpaceSidebarItemProps) => {
|
||||
const children = sortSpaceChildren(space, nodeChildrenGetQuery.data ?? []);
|
||||
|
||||
const handleDragEnd = (childId: string, after: string | null) => {
|
||||
mutation.mutate({
|
||||
input: {
|
||||
type: 'space.child.reorder',
|
||||
userId: workspace.userId,
|
||||
spaceId: space.id,
|
||||
childId,
|
||||
after,
|
||||
},
|
||||
onError(error) {
|
||||
toast.error(error.message);
|
||||
},
|
||||
const nodes = database.workspace(workspace.userId).nodes;
|
||||
if (!nodes.has(space.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const children: LocalNode[] = [];
|
||||
for (const [, node] of nodes.entries()) {
|
||||
if (node.parentId === space.id) {
|
||||
children.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
const newIndex = generateSpaceChildIndex(space, children, childId, after);
|
||||
|
||||
nodes.update(space.id, (draft) => {
|
||||
if (draft.attributes.type !== 'space') {
|
||||
return;
|
||||
}
|
||||
|
||||
const childrenSettings = draft.attributes.children ?? {};
|
||||
childrenSettings[childId] = {
|
||||
...(childrenSettings[childId] ?? {}),
|
||||
id: childId,
|
||||
index: newIndex,
|
||||
};
|
||||
|
||||
draft.attributes.children = childrenSettings;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ import { createContext, useContext } from 'react';
|
||||
import {
|
||||
DatabaseNameFieldAttributes,
|
||||
FieldAttributes,
|
||||
FieldType,
|
||||
NodeRole,
|
||||
SelectOptionAttributes,
|
||||
} from '@colanode/core';
|
||||
|
||||
interface DatabaseContext {
|
||||
@@ -16,16 +14,6 @@ interface DatabaseContext {
|
||||
canEdit: boolean;
|
||||
canCreateRecord: boolean;
|
||||
role: NodeRole;
|
||||
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;
|
||||
updateSelectOption: (
|
||||
fieldId: string,
|
||||
attributes: SelectOptionAttributes
|
||||
) => void;
|
||||
deleteSelectOption: (fieldId: string, optionId: string) => void;
|
||||
}
|
||||
|
||||
export const DatabaseContext = createContext<DatabaseContext>(
|
||||
|
||||
@@ -49,26 +49,38 @@ export const createNodesCollection = (userId: string) => {
|
||||
},
|
||||
},
|
||||
onInsert: async ({ transaction }) => {
|
||||
await Promise.all(
|
||||
transaction.mutations.map(async (mutation) => {
|
||||
return await window.colanode.executeMutation({
|
||||
type: 'node.create',
|
||||
userId,
|
||||
node: mutation.modified,
|
||||
});
|
||||
})
|
||||
);
|
||||
transaction.mutations.forEach(async (mutation) => {
|
||||
await window.colanode.executeMutation({
|
||||
type: 'node.create',
|
||||
userId,
|
||||
node: mutation.modified,
|
||||
});
|
||||
});
|
||||
},
|
||||
onUpdate: async ({ transaction }) => {
|
||||
transaction.mutations.forEach(async (mutation) => {
|
||||
console.log('onUpdate', mutation);
|
||||
const attributes = mutation.changes.attributes;
|
||||
if (!attributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
await window.colanode.executeMutation({
|
||||
type: 'node.update',
|
||||
userId,
|
||||
nodeId: mutation.key,
|
||||
attributes,
|
||||
});
|
||||
});
|
||||
},
|
||||
onDelete: async ({ transaction }) => {
|
||||
await Promise.all(
|
||||
transaction.mutations.map(async (mutation) => {
|
||||
return await window.colanode.executeMutation({
|
||||
type: 'node.delete',
|
||||
userId,
|
||||
nodeId: mutation.key,
|
||||
});
|
||||
})
|
||||
);
|
||||
transaction.mutations.forEach(async (mutation) => {
|
||||
await window.colanode.executeMutation({
|
||||
type: 'node.delete',
|
||||
userId,
|
||||
nodeId: mutation.key,
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { LocalNode, LocalSpaceNode } from '@colanode/client/types';
|
||||
import { compareString, generateFractionalIndex } from '@colanode/core';
|
||||
|
||||
interface NodeFractionalIndex {
|
||||
id: string;
|
||||
defaultIndex: string;
|
||||
customIndex: string | null;
|
||||
}
|
||||
|
||||
export const sortSpaceChildren = (
|
||||
space: LocalSpaceNode,
|
||||
children: LocalNode[]
|
||||
@@ -22,3 +28,84 @@ export const sortSpaceChildren = (
|
||||
return compareString(aIndex, bIndex);
|
||||
});
|
||||
};
|
||||
|
||||
export const generateSpaceChildIndex = (
|
||||
space: LocalSpaceNode,
|
||||
children: LocalNode[],
|
||||
childId: string,
|
||||
after: string | null
|
||||
): string | null => {
|
||||
const child = children.find((c) => c.id === childId);
|
||||
if (!child) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sortedById = children.toSorted((a, b) => compareString(a.id, b.id));
|
||||
const indexes: NodeFractionalIndex[] = [];
|
||||
const childrenSettings = space.attributes.children ?? {};
|
||||
let lastIndex: string | null = null;
|
||||
|
||||
for (const child of sortedById) {
|
||||
lastIndex = generateFractionalIndex(lastIndex, null);
|
||||
indexes.push({
|
||||
id: child.id,
|
||||
defaultIndex: lastIndex,
|
||||
customIndex: childrenSettings[child.id]?.index ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
const sortedIndexes = indexes.sort((a, b) =>
|
||||
compareString(
|
||||
a.customIndex ?? a.defaultIndex,
|
||||
b.customIndex ?? b.defaultIndex
|
||||
)
|
||||
);
|
||||
|
||||
if (after === null) {
|
||||
const firstIndex = sortedIndexes[0];
|
||||
if (!firstIndex) {
|
||||
return generateFractionalIndex(null, null);
|
||||
}
|
||||
|
||||
const nextIndex = firstIndex.customIndex ?? firstIndex.defaultIndex;
|
||||
return generateFractionalIndex(null, nextIndex);
|
||||
}
|
||||
|
||||
const afterNodeIndex = sortedIndexes.findIndex((node) => node.id === after);
|
||||
if (afterNodeIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const afterNode = sortedIndexes[afterNodeIndex];
|
||||
if (!afterNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const previousIndex = afterNode.customIndex ?? afterNode.defaultIndex;
|
||||
let nextIndex: string | null = null;
|
||||
if (afterNodeIndex < sortedIndexes.length - 1) {
|
||||
const nextNode = sortedIndexes[afterNodeIndex + 1];
|
||||
if (!nextNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nextIndex = nextNode.customIndex ?? nextNode.defaultIndex;
|
||||
}
|
||||
|
||||
let newIndex = generateFractionalIndex(previousIndex, nextIndex);
|
||||
|
||||
const maxDefaultIndex = sortedIndexes
|
||||
.map((index) => index.defaultIndex)
|
||||
.sort((a, b) => -compareString(a, b))[0]!;
|
||||
|
||||
const newPotentialDefaultIndex = generateFractionalIndex(
|
||||
maxDefaultIndex,
|
||||
null
|
||||
);
|
||||
|
||||
if (newPotentialDefaultIndex === newIndex) {
|
||||
newIndex = generateFractionalIndex(previousIndex, newPotentialDefaultIndex);
|
||||
}
|
||||
|
||||
return newIndex;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user