Use paced mutations for some rename inputs

This commit is contained in:
Hakan Shehu
2025-11-21 09:25:24 -08:00
parent 019b16f428
commit b784397d30
7 changed files with 142 additions and 73 deletions

View File

@@ -1,8 +1,7 @@
import { createCollection } from '@tanstack/react-db';
import { cloneDeep } from 'lodash-es';
import { mapNodeAttributes } from '@colanode/client/lib';
import { LocalNode } from '@colanode/client/types';
import { applyNodeTransaction } from '@colanode/ui/lib/nodes';
export const createNodesCollection = (userId: string) => {
return createCollection<LocalNode, string>({
@@ -62,37 +61,14 @@ export const createNodesCollection = (userId: string) => {
},
},
onInsert: async ({ transaction }) => {
for (const mutation of transaction.mutations) {
const node = mutation.modified;
const attributes = mapNodeAttributes(node);
await window.colanode.executeMutation({
type: 'node.create',
userId,
nodeId: node.id,
attributes,
});
}
await applyNodeTransaction(userId, transaction);
},
onUpdate: async ({ transaction }) => {
for (const mutation of transaction.mutations) {
const node = cloneDeep(mutation.modified);
const attributes = mapNodeAttributes(node);
await window.colanode.executeMutation({
type: 'node.update',
userId,
nodeId: mutation.key,
attributes,
});
}
console.log('onUpdate', transaction);
await applyNodeTransaction(userId, transaction);
},
onDelete: async ({ transaction }) => {
for (const mutation of transaction.mutations) {
await window.colanode.executeMutation({
type: 'node.delete',
userId,
nodeId: mutation.key,
});
}
await applyNodeTransaction(userId, transaction);
},
});
};

View File

@@ -5,6 +5,7 @@ import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
import { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
import { ViewIcon } from '@colanode/ui/components/databases/view-icon';
import { ViewRenameInput } from '@colanode/ui/components/databases/view-rename-input';
import { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import { Button } from '@colanode/ui/components/ui/button';
@@ -14,7 +15,6 @@ import {
PopoverTrigger,
} from '@colanode/ui/components/ui/popover';
import { Separator } from '@colanode/ui/components/ui/separator';
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
import {
Tooltip,
TooltipContent,
@@ -67,14 +67,10 @@ export const BoardViewSettings = () => {
/>
</Button>
)}
<SmartTextInput
value={view.name}
<ViewRenameInput
id={view.id}
name={view.name}
readOnly={!database.canEdit}
onChange={(newName) => {
if (newName === view.name) return;
view.rename(newName);
}}
/>
</div>
<Separator />

View File

@@ -5,6 +5,7 @@ import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
import { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
import { ViewIcon } from '@colanode/ui/components/databases/view-icon';
import { ViewRenameInput } from '@colanode/ui/components/databases/view-rename-input';
import { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import { Button } from '@colanode/ui/components/ui/button';
@@ -14,7 +15,6 @@ import {
PopoverTrigger,
} from '@colanode/ui/components/ui/popover';
import { Separator } from '@colanode/ui/components/ui/separator';
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
import {
Tooltip,
TooltipContent,
@@ -67,14 +67,10 @@ export const CalendarViewSettings = () => {
/>
</Button>
)}
<SmartTextInput
value={view.name}
<ViewRenameInput
id={view.id}
name={view.name}
readOnly={!database.canEdit}
onChange={(newName) => {
if (newName === view.name) return;
view.rename(newName);
}}
/>
</div>
<Separator />

View File

@@ -1,7 +1,12 @@
import { debounceStrategy, usePacedMutations } from '@tanstack/react-db';
import { useEffect, useState } from 'react';
import { LocalNode } from '@colanode/client/types';
import { FieldAttributes } from '@colanode/core';
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
import { Input } from '@colanode/ui/components/ui/input';
import { useDatabase } from '@colanode/ui/contexts/database';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { applyNodeTransaction } from '@colanode/ui/lib/nodes';
interface FieldRenameInputProps {
field: FieldAttributes;
@@ -10,17 +15,15 @@ interface FieldRenameInputProps {
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
const workspace = useWorkspace();
const database = useDatabase();
const [name, setName] = useState(field.name);
return (
<div className="w-full p-1">
<SmartTextInput
value={field.name}
readOnly={!database.canEdit}
onChange={(newName) => {
if (newName === field.name) return;
useEffect(() => {
setName(field.name);
}, [field.name]);
const nodes = workspace.collections.nodes;
nodes.update(database.id, (draft) => {
const mutate = usePacedMutations<string, LocalNode>({
onMutate: (value) => {
workspace.collections.nodes.update(database.id, (draft) => {
if (draft.type !== 'database') {
return;
}
@@ -30,8 +33,24 @@ export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
return;
}
fieldAttributes.name = newName;
fieldAttributes.name = value;
});
},
mutationFn: async ({ transaction }) => {
await applyNodeTransaction(workspace.userId, transaction);
},
strategy: debounceStrategy({ wait: 500 }),
});
return (
<div className="w-full p-1">
<Input
value={name}
readOnly={!database.canEdit}
onChange={(event) => {
const newValue = event.target.value;
setName(newValue);
mutate(newValue);
}}
/>
</div>

View File

@@ -5,6 +5,7 @@ import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
import { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
import { ViewIcon } from '@colanode/ui/components/databases/view-icon';
import { ViewRenameInput } from '@colanode/ui/components/databases/view-rename-input';
import { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import { Button } from '@colanode/ui/components/ui/button';
@@ -14,7 +15,6 @@ import {
PopoverTrigger,
} from '@colanode/ui/components/ui/popover';
import { Separator } from '@colanode/ui/components/ui/separator';
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
import {
Tooltip,
TooltipContent,
@@ -67,14 +67,10 @@ export const TableViewSettings = () => {
/>
</Button>
)}
<SmartTextInput
value={view.name}
<ViewRenameInput
id={view.id}
name={view.name}
readOnly={!database.canEdit}
onChange={(newName) => {
if (newName === view.name) return;
view.rename(newName);
}}
/>
</div>
<Separator />

View File

@@ -0,0 +1,49 @@
import { debounceStrategy, usePacedMutations } from '@tanstack/react-db';
import { LocalNode } from '@colanode/client/types';
import { Input } from '@colanode/ui/components/ui/input';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { applyNodeTransaction } from '@colanode/ui/lib/nodes';
interface ViewRenameInputProps {
id: string;
name: string;
readOnly?: boolean;
}
export const ViewRenameInput = ({
id,
name,
readOnly,
}: ViewRenameInputProps) => {
const workspace = useWorkspace();
const mutate = usePacedMutations<string, LocalNode>({
onMutate: (value) => {
workspace.collections.nodes.update(id, (draft) => {
if (draft.type !== 'database_view') {
return;
}
draft.name = value;
});
},
mutationFn: async ({ transaction }) => {
await applyNodeTransaction(workspace.userId, transaction);
},
strategy: debounceStrategy({ wait: 500 }),
});
return (
<div className="w-full p-1">
<Input
value={name}
readOnly={readOnly}
onChange={(event) => {
const newValue = event.target.value;
mutate(newValue);
}}
/>
</div>
);
};

View File

@@ -1,4 +1,8 @@
import { NodeCollaborator } from '@colanode/client/types';
import { OperationType, TransactionWithMutations } from '@tanstack/react-db';
import { cloneDeep } from 'lodash-es';
import { mapNodeAttributes } from '@colanode/client/lib';
import { LocalNode, NodeCollaborator } from '@colanode/client/types';
import { extractNodeCollaborators, Node } from '@colanode/core';
export const buildNodeCollaborators = (nodes: Node[]): NodeCollaborator[] => {
@@ -18,3 +22,36 @@ export const buildNodeCollaborators = (nodes: Node[]): NodeCollaborator[] => {
return Object.values(collaborators);
};
export const applyNodeTransaction = async (
userId: string,
transaction: TransactionWithMutations<LocalNode, OperationType>
) => {
for (const mutation of transaction.mutations) {
if (mutation.type === 'insert') {
const node = mutation.modified;
const attributes = mapNodeAttributes(node);
await window.colanode.executeMutation({
type: 'node.create',
userId,
nodeId: node.id,
attributes,
});
} else if (mutation.type === 'update') {
const node = cloneDeep(mutation.modified);
const attributes = mapNodeAttributes(node);
await window.colanode.executeMutation({
type: 'node.update',
userId,
nodeId: mutation.key,
attributes,
});
} else if (mutation.type === 'delete') {
await window.colanode.executeMutation({
type: 'node.delete',
userId,
nodeId: mutation.key,
});
}
}
};