Improve node updates

This commit is contained in:
Hakan Shehu
2024-09-13 01:22:29 +02:00
parent 8c59430d86
commit 261fd37cf5
12 changed files with 97 additions and 204 deletions

View File

@@ -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>

View File

@@ -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)} />

View File

@@ -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"
/>
);
};

View File

@@ -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"
/>
);
};

View File

@@ -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"
/>
);
};

View File

@@ -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"
/>
);
};

View File

@@ -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

View File

@@ -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);

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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);
},
});
};

View File

@@ -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);
},
});
};