mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Improve node updates
This commit is contained in:
@@ -1,33 +1,32 @@
|
||||
import React from 'react';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { FieldNode } from '@/types/databases';
|
||||
import { useNodeUpdateNameMutation } from '@/mutations/use-node-update-name-mutation';
|
||||
import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation';
|
||||
import { SmartTextInput } from '@/components/ui/smart-text-input';
|
||||
import { AttributeTypes } from '@/lib/constants';
|
||||
|
||||
interface FieldRenameInputProps {
|
||||
field: FieldNode;
|
||||
}
|
||||
|
||||
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
||||
const { mutate, isPending } = useNodeUpdateNameMutation();
|
||||
const [name, setName] = React.useState(field.name);
|
||||
const { mutate, isPending } = useNodeAttributeUpsertMutation();
|
||||
|
||||
return (
|
||||
<div className="w-full p-1">
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (name !== field.name && !isPending) {
|
||||
mutate({ id: field.id, name });
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isHotkey('enter', e) && name !== field.name && !isPending) {
|
||||
mutate({ id: field.id, name });
|
||||
}
|
||||
<SmartTextInput
|
||||
value={field.name}
|
||||
onChange={(newName) => {
|
||||
if (isPending) return;
|
||||
if (newName === field.name) return;
|
||||
|
||||
mutate({
|
||||
nodeId: field.id,
|
||||
type: AttributeTypes.Name,
|
||||
key: '1',
|
||||
textValue: newName,
|
||||
numberValue: null,
|
||||
foreignNodeId: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import { Icon } from '@/components/ui/icon';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Popover,
|
||||
@@ -13,25 +11,19 @@ import { selectOptionColors } from '@/lib/databases';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SelectOptionNode } from '@/types/databases';
|
||||
import { SelectOptionDeleteDialog } from '@/components/databases/fields/select-option-delete-dialog';
|
||||
import { useNodeUpdateNameMutation } from '@/mutations/use-node-update-name-mutation';
|
||||
import { useUpdateSelectOptionColorMutation } from '@/mutations/use-update-select-option-color-mutation';
|
||||
import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation';
|
||||
import { SmartTextInput } from '@/components/ui/smart-text-input';
|
||||
import { AttributeTypes } from '@/lib/constants';
|
||||
|
||||
interface SelectOptionSettingsPopoverProps {
|
||||
option: SelectOptionNode;
|
||||
}
|
||||
|
||||
interface UpdateSelectOptionInput {
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export const SelectOptionSettingsPopover = ({
|
||||
option,
|
||||
}: SelectOptionSettingsPopoverProps) => {
|
||||
const nameMutation = useNodeUpdateNameMutation();
|
||||
const colorMutation = useUpdateSelectOptionColorMutation();
|
||||
const { mutate, isPending } = useNodeAttributeUpsertMutation();
|
||||
|
||||
const [name, setName] = React.useState(option.name);
|
||||
const [openSetttingsPopover, setOpenSetttingsPopover] = React.useState(false);
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = React.useState(false);
|
||||
|
||||
@@ -47,29 +39,20 @@ export const SelectOptionSettingsPopover = ({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="ml-1 flex w-72 flex-col gap-1 p-2 text-sm">
|
||||
<div className="p-1">
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (name !== option.name && !nameMutation.isPending) {
|
||||
nameMutation.mutate({
|
||||
id: option.id,
|
||||
name,
|
||||
});
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isHotkey('enter', e)) {
|
||||
if (name !== option.name && !nameMutation.isPending) {
|
||||
nameMutation.mutate({
|
||||
id: option.id,
|
||||
name,
|
||||
});
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
<SmartTextInput
|
||||
value={option.name}
|
||||
onChange={(newName) => {
|
||||
if (isPending) return;
|
||||
if (newName === option.name) return;
|
||||
|
||||
mutate({
|
||||
nodeId: option.id,
|
||||
type: AttributeTypes.Name,
|
||||
key: '1',
|
||||
textValue: newName,
|
||||
numberValue: null,
|
||||
foreignNodeId: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -81,15 +64,15 @@ export const SelectOptionSettingsPopover = ({
|
||||
key={color.value}
|
||||
className="flex cursor-pointer flex-row items-center gap-2 rounded-md p-1 hover:bg-gray-100"
|
||||
onClick={() => {
|
||||
if (
|
||||
color.value !== option.color &&
|
||||
!colorMutation.isPending
|
||||
) {
|
||||
colorMutation.mutate({
|
||||
id: option.id,
|
||||
color: color.value,
|
||||
});
|
||||
}
|
||||
if (isPending) return;
|
||||
mutate({
|
||||
nodeId: option.id,
|
||||
type: AttributeTypes.Color,
|
||||
key: '1',
|
||||
textValue: color.value,
|
||||
numberValue: null,
|
||||
foreignNodeId: null,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className={cn('h-4 w-4 rounded-md', color.class)} />
|
||||
|
||||
@@ -51,7 +51,7 @@ export const TableViewEmailCell = ({
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text"
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 border-none p-1 text-sm focus-visible:cursor-text"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@ export const TableViewNumberCell = ({
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text"
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 border-none p-1 text-sm focus-visible:cursor-text"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ export const TableViewPhoneCell = ({
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text"
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 border-none p-1 text-sm focus-visible:cursor-text"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ export const TableViewTextCell = ({
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text"
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 border-none p-1 text-sm focus-visible:cursor-text"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -63,7 +63,7 @@ export const TableViewUrlCell = ({ record, field }: TableViewUrlCellProps) => {
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text"
|
||||
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 border-none p-1 text-sm focus-visible:cursor-text"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
|
||||
@@ -4,7 +4,8 @@ import { Icon } from '@/components/ui/icon';
|
||||
import { useWorkspace } from '@/contexts/workspace';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { RecordNode } from '@/types/databases';
|
||||
import { useNodeUpdateNameMutation } from '@/mutations/use-node-update-name-mutation';
|
||||
import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation';
|
||||
import { AttributeTypes } from '@/lib/constants';
|
||||
|
||||
interface NameEditorProps {
|
||||
initialValue: string;
|
||||
@@ -55,7 +56,7 @@ export const TableViewNameCell = ({ record }: TableViewNameCellProps) => {
|
||||
const [isEditing, setIsEditing] = React.useState(false);
|
||||
const [name, setName] = React.useState(record.name);
|
||||
|
||||
const { mutate, isPending } = useNodeUpdateNameMutation();
|
||||
const { mutate, isPending } = useNodeAttributeUpsertMutation();
|
||||
|
||||
React.useEffect(() => {
|
||||
setName(record.name);
|
||||
@@ -67,7 +68,14 @@ export const TableViewNameCell = ({ record }: TableViewNameCellProps) => {
|
||||
const handleSave = (newName: string) => {
|
||||
setName(newName);
|
||||
mutate(
|
||||
{ id: record.id, name: newName },
|
||||
{
|
||||
nodeId: record.id,
|
||||
type: AttributeTypes.Name,
|
||||
key: '1',
|
||||
textValue: newName,
|
||||
numberValue: null,
|
||||
foreignNodeId: null,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setIsEditing(false);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from 'react';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { Icon } from '@/components/ui/icon';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useTableView } from '@/contexts/table-view';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { useDatabase } from '@/contexts/database';
|
||||
@@ -18,15 +16,16 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { FieldDeleteDialog } from '@/components/databases/fields/field-delete-dialog';
|
||||
import { useNodeUpdateNameMutation } from '@/mutations/use-node-update-name-mutation';
|
||||
import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation';
|
||||
import { ViewDeleteDialog } from '@/components/databases/view-delete-dialog';
|
||||
import { SmartTextInput } from '@/components/ui/smart-text-input';
|
||||
import { AttributeTypes } from '@/lib/constants';
|
||||
|
||||
export const TableViewSettingsPopover = () => {
|
||||
const tableView = useTableView();
|
||||
const database = useDatabase();
|
||||
const nodeUpdateNameMutation = useNodeUpdateNameMutation();
|
||||
const { mutate, isPending } = useNodeAttributeUpsertMutation();
|
||||
|
||||
const [name, setName] = React.useState(tableView.name);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [openDelete, setOpenDelete] = React.useState(false);
|
||||
const [deleteFieldId, setDeleteFieldId] = React.useState<string | null>(null);
|
||||
@@ -44,27 +43,20 @@ export const TableViewSettingsPopover = () => {
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="mr-4 flex w-[600px] flex-col gap-1.5 p-2">
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (
|
||||
name !== tableView.name &&
|
||||
!nodeUpdateNameMutation.isPending
|
||||
) {
|
||||
nodeUpdateNameMutation.mutate({ id: tableView.id, name });
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
isHotkey('enter', e) &&
|
||||
name !== tableView.name &&
|
||||
!nodeUpdateNameMutation.isPending
|
||||
) {
|
||||
nodeUpdateNameMutation.mutate({ id: tableView.id, name });
|
||||
}
|
||||
<SmartTextInput
|
||||
value={tableView.name}
|
||||
onChange={(newName) => {
|
||||
if (isPending) return;
|
||||
if (newName === tableView.name) return;
|
||||
|
||||
mutate({
|
||||
nodeId: tableView.id,
|
||||
type: AttributeTypes.Name,
|
||||
key: '1',
|
||||
textValue: newName,
|
||||
numberValue: null,
|
||||
foreignNodeId: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Separator />
|
||||
|
||||
@@ -1,37 +1,32 @@
|
||||
import React from 'react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { debounce } from 'lodash';
|
||||
import { BreadcrumbNode } from '@/types/workspaces';
|
||||
import { useNodeUpdateNameMutation } from '@/mutations/use-node-update-name-mutation';
|
||||
import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation';
|
||||
import { SmartTextInput } from '@/components/ui/smart-text-input';
|
||||
import { AttributeTypes } from '@/lib/constants';
|
||||
|
||||
interface BreadcrumbItemEditorProps {
|
||||
node: BreadcrumbNode;
|
||||
}
|
||||
|
||||
export const BreadcrumbItemEditor = ({ node }: BreadcrumbItemEditorProps) => {
|
||||
const [name, setName] = React.useState(node.name ?? '');
|
||||
const { mutate } = useNodeUpdateNameMutation();
|
||||
|
||||
const handleNameChange = React.useMemo(
|
||||
() =>
|
||||
debounce(async (newName: string) => {
|
||||
mutate({
|
||||
id: node.id,
|
||||
name: newName,
|
||||
});
|
||||
}, 500),
|
||||
[node.id],
|
||||
);
|
||||
const { mutate, isPending } = useNodeAttributeUpsertMutation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={async (e) => {
|
||||
const newName = e.target.value;
|
||||
setName(newName);
|
||||
await handleNameChange(newName);
|
||||
<SmartTextInput
|
||||
value={node.name}
|
||||
onChange={(newName) => {
|
||||
if (isPending) return;
|
||||
if (newName === node.name) return;
|
||||
|
||||
mutate({
|
||||
nodeId: node.id,
|
||||
type: AttributeTypes.Name,
|
||||
key: '1',
|
||||
textValue: newName,
|
||||
numberValue: null,
|
||||
foreignNodeId: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useWorkspace } from '@/contexts/workspace';
|
||||
import { AttributeTypes } from '@/lib/constants';
|
||||
import { NeuronId } from '@/lib/id';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
interface NodeUpdateNameInput {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const useNodeUpdateNameMutation = () => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (input: NodeUpdateNameInput) => {
|
||||
const { id, name } = input;
|
||||
|
||||
const query = workspace.schema
|
||||
.insertInto('node_attributes')
|
||||
.values({
|
||||
node_id: id,
|
||||
type: AttributeTypes.Name,
|
||||
key: '1',
|
||||
text_value: name,
|
||||
created_at: new Date().toISOString(),
|
||||
created_by: workspace.userId,
|
||||
version_id: NeuronId.generate(NeuronId.Type.Version),
|
||||
})
|
||||
.onConflict((b) =>
|
||||
b.doUpdateSet({
|
||||
text_value: name,
|
||||
updated_at: new Date().toISOString(),
|
||||
updated_by: workspace.userId,
|
||||
version_id: NeuronId.generate(NeuronId.Type.Version),
|
||||
}),
|
||||
)
|
||||
.compile();
|
||||
|
||||
await workspace.mutate(query);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useWorkspace } from '@/contexts/workspace';
|
||||
import { AttributeTypes } from '@/lib/constants';
|
||||
import { NeuronId } from '@/lib/id';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
interface UpdateSelectOptionColorInput {
|
||||
id: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export const useUpdateSelectOptionColorMutation = () => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (input: UpdateSelectOptionColorInput) => {
|
||||
const { id, color } = input;
|
||||
|
||||
const query = workspace.schema
|
||||
.insertInto('node_attributes')
|
||||
.values({
|
||||
node_id: id,
|
||||
type: AttributeTypes.Color,
|
||||
key: '1',
|
||||
text_value: color,
|
||||
created_at: new Date().toISOString(),
|
||||
created_by: workspace.userId,
|
||||
version_id: NeuronId.generate(NeuronId.Type.Version),
|
||||
})
|
||||
.onConflict((b) =>
|
||||
b.doUpdateSet({
|
||||
text_value: color,
|
||||
updated_at: new Date().toISOString(),
|
||||
updated_by: workspace.userId,
|
||||
version_id: NeuronId.generate(NeuronId.Type.Version),
|
||||
}),
|
||||
)
|
||||
.compile();
|
||||
|
||||
await workspace.mutate(query);
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user