mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
Use paced mutations for some rename inputs
This commit is contained in:
@@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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,28 +15,42 @@ interface FieldRenameInputProps {
|
||||
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
const [name, setName] = useState(field.name);
|
||||
|
||||
useEffect(() => {
|
||||
setName(field.name);
|
||||
}, [field.name]);
|
||||
|
||||
const mutate = usePacedMutations<string, LocalNode>({
|
||||
onMutate: (value) => {
|
||||
workspace.collections.nodes.update(database.id, (draft) => {
|
||||
if (draft.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldAttributes = draft.fields[field.id];
|
||||
if (!fieldAttributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldAttributes.name = value;
|
||||
});
|
||||
},
|
||||
mutationFn: async ({ transaction }) => {
|
||||
await applyNodeTransaction(workspace.userId, transaction);
|
||||
},
|
||||
strategy: debounceStrategy({ wait: 500 }),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full p-1">
|
||||
<SmartTextInput
|
||||
value={field.name}
|
||||
<Input
|
||||
value={name}
|
||||
readOnly={!database.canEdit}
|
||||
onChange={(newName) => {
|
||||
if (newName === field.name) return;
|
||||
|
||||
const nodes = workspace.collections.nodes;
|
||||
nodes.update(database.id, (draft) => {
|
||||
if (draft.type !== 'database') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldAttributes = draft.fields[field.id];
|
||||
if (!fieldAttributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldAttributes.name = newName;
|
||||
});
|
||||
onChange={(event) => {
|
||||
const newValue = event.target.value;
|
||||
setName(newValue);
|
||||
mutate(newValue);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
49
packages/ui/src/components/databases/view-rename-input.tsx
Normal file
49
packages/ui/src/components/databases/view-rename-input.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user