mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 19:57:46 +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 { createCollection } from '@tanstack/react-db';
|
||||||
import { cloneDeep } from 'lodash-es';
|
|
||||||
|
|
||||||
import { mapNodeAttributes } from '@colanode/client/lib';
|
|
||||||
import { LocalNode } from '@colanode/client/types';
|
import { LocalNode } from '@colanode/client/types';
|
||||||
|
import { applyNodeTransaction } from '@colanode/ui/lib/nodes';
|
||||||
|
|
||||||
export const createNodesCollection = (userId: string) => {
|
export const createNodesCollection = (userId: string) => {
|
||||||
return createCollection<LocalNode, string>({
|
return createCollection<LocalNode, string>({
|
||||||
@@ -62,37 +61,14 @@ export const createNodesCollection = (userId: string) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
onInsert: async ({ transaction }) => {
|
onInsert: async ({ transaction }) => {
|
||||||
for (const mutation of transaction.mutations) {
|
await applyNodeTransaction(userId, transaction);
|
||||||
const node = mutation.modified;
|
|
||||||
const attributes = mapNodeAttributes(node);
|
|
||||||
await window.colanode.executeMutation({
|
|
||||||
type: 'node.create',
|
|
||||||
userId,
|
|
||||||
nodeId: node.id,
|
|
||||||
attributes,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onUpdate: async ({ transaction }) => {
|
onUpdate: async ({ transaction }) => {
|
||||||
for (const mutation of transaction.mutations) {
|
console.log('onUpdate', transaction);
|
||||||
const node = cloneDeep(mutation.modified);
|
await applyNodeTransaction(userId, transaction);
|
||||||
const attributes = mapNodeAttributes(node);
|
|
||||||
await window.colanode.executeMutation({
|
|
||||||
type: 'node.update',
|
|
||||||
userId,
|
|
||||||
nodeId: mutation.key,
|
|
||||||
attributes,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onDelete: async ({ transaction }) => {
|
onDelete: async ({ transaction }) => {
|
||||||
for (const mutation of transaction.mutations) {
|
await applyNodeTransaction(userId, transaction);
|
||||||
await window.colanode.executeMutation({
|
|
||||||
type: 'node.delete',
|
|
||||||
userId,
|
|
||||||
nodeId: mutation.key,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
|
||||||
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
||||||
import { ViewIcon } from '@colanode/ui/components/databases/view-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 { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
|
||||||
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@colanode/ui/components/ui/popover';
|
} from '@colanode/ui/components/ui/popover';
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -67,14 +67,10 @@ export const BoardViewSettings = () => {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<SmartTextInput
|
<ViewRenameInput
|
||||||
value={view.name}
|
id={view.id}
|
||||||
|
name={view.name}
|
||||||
readOnly={!database.canEdit}
|
readOnly={!database.canEdit}
|
||||||
onChange={(newName) => {
|
|
||||||
if (newName === view.name) return;
|
|
||||||
|
|
||||||
view.rename(newName);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<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 { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
|
||||||
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
||||||
import { ViewIcon } from '@colanode/ui/components/databases/view-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 { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
|
||||||
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@colanode/ui/components/ui/popover';
|
} from '@colanode/ui/components/ui/popover';
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -67,14 +67,10 @@ export const CalendarViewSettings = () => {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<SmartTextInput
|
<ViewRenameInput
|
||||||
value={view.name}
|
id={view.id}
|
||||||
|
name={view.name}
|
||||||
readOnly={!database.canEdit}
|
readOnly={!database.canEdit}
|
||||||
onChange={(newName) => {
|
|
||||||
if (newName === view.name) return;
|
|
||||||
|
|
||||||
view.rename(newName);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<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 { 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 { useDatabase } from '@colanode/ui/contexts/database';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
import { applyNodeTransaction } from '@colanode/ui/lib/nodes';
|
||||||
|
|
||||||
interface FieldRenameInputProps {
|
interface FieldRenameInputProps {
|
||||||
field: FieldAttributes;
|
field: FieldAttributes;
|
||||||
@@ -10,28 +15,42 @@ interface FieldRenameInputProps {
|
|||||||
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const database = useDatabase();
|
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 (
|
return (
|
||||||
<div className="w-full p-1">
|
<div className="w-full p-1">
|
||||||
<SmartTextInput
|
<Input
|
||||||
value={field.name}
|
value={name}
|
||||||
readOnly={!database.canEdit}
|
readOnly={!database.canEdit}
|
||||||
onChange={(newName) => {
|
onChange={(event) => {
|
||||||
if (newName === field.name) return;
|
const newValue = event.target.value;
|
||||||
|
setName(newValue);
|
||||||
const nodes = workspace.collections.nodes;
|
mutate(newValue);
|
||||||
nodes.update(database.id, (draft) => {
|
|
||||||
if (draft.type !== 'database') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldAttributes = draft.fields[field.id];
|
|
||||||
if (!fieldAttributes) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldAttributes.name = newName;
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { FieldDeleteDialog } from '@colanode/ui/components/databases/fields/field-delete-dialog';
|
||||||
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
import { FieldIcon } from '@colanode/ui/components/databases/fields/field-icon';
|
||||||
import { ViewIcon } from '@colanode/ui/components/databases/view-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 { ViewSettingsButton } from '@colanode/ui/components/databases/view-settings-button';
|
||||||
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@colanode/ui/components/ui/popover';
|
} from '@colanode/ui/components/ui/popover';
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -67,14 +67,10 @@ export const TableViewSettings = () => {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<SmartTextInput
|
<ViewRenameInput
|
||||||
value={view.name}
|
id={view.id}
|
||||||
|
name={view.name}
|
||||||
readOnly={!database.canEdit}
|
readOnly={!database.canEdit}
|
||||||
onChange={(newName) => {
|
|
||||||
if (newName === view.name) return;
|
|
||||||
|
|
||||||
view.rename(newName);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<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';
|
import { extractNodeCollaborators, Node } from '@colanode/core';
|
||||||
|
|
||||||
export const buildNodeCollaborators = (nodes: Node[]): NodeCollaborator[] => {
|
export const buildNodeCollaborators = (nodes: Node[]): NodeCollaborator[] => {
|
||||||
@@ -18,3 +22,36 @@ export const buildNodeCollaborators = (nodes: Node[]): NodeCollaborator[] => {
|
|||||||
|
|
||||||
return Object.values(collaborators);
|
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