mirror of
https://github.com/chartdb/chartdb.git
synced 2026-04-28 21:59:40 -05:00
fix(canvas edit): add option to edit names in canvas (#536)
* fix(canvas edit): add option to edit names in canvas * fix(canvas edit): add option to edit field names in canvas * some few fixes + style --------- Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
Handle,
|
||||
Position,
|
||||
@@ -6,7 +12,7 @@ import {
|
||||
useUpdateNodeInternals,
|
||||
} from '@xyflow/react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { KeyRound, MessageCircleMore, Trash2 } from 'lucide-react';
|
||||
import { Check, KeyRound, MessageCircleMore, Trash2 } from 'lucide-react';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -15,6 +21,8 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import { useClickAway, useKeyPressEvent } from 'react-use';
|
||||
import { Input } from '@/components/input/input';
|
||||
|
||||
export const LEFT_HANDLE_ID_PREFIX = 'left_rel_';
|
||||
export const RIGHT_HANDLE_ID_PREFIX = 'right_rel_';
|
||||
@@ -31,7 +39,12 @@ export interface TableNodeFieldProps {
|
||||
|
||||
export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
|
||||
({ field, focused, tableNodeId, highlighted, visible, isConnectable }) => {
|
||||
const { removeField, relationships, readonly } = useChartDB();
|
||||
const { removeField, relationships, readonly, updateField } =
|
||||
useChartDB();
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [fieldName, setFieldName] = useState(field.name);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const connection = useConnection();
|
||||
const isTarget = useMemo(
|
||||
@@ -65,6 +78,28 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
|
||||
}
|
||||
}, [tableNodeId, updateNodeInternals, numberOfEdgesToField]);
|
||||
|
||||
const editFieldName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (fieldName.trim()) {
|
||||
updateField(tableNodeId, field.id, { name: fieldName.trim() });
|
||||
}
|
||||
setEditMode(false);
|
||||
}, [fieldName, field.id, updateField, editMode, tableNodeId]);
|
||||
|
||||
const abortEdit = useCallback(() => {
|
||||
setEditMode(false);
|
||||
setFieldName(field.name);
|
||||
}, [field.name]);
|
||||
|
||||
useClickAway(inputRef, editFieldName);
|
||||
useKeyPressEvent('Enter', editFieldName);
|
||||
useKeyPressEvent('Escape', abortEdit);
|
||||
|
||||
const enterEditMode = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group relative flex h-8 items-center justify-between gap-1 border-t px-3 text-sm last:rounded-b-[6px] hover:bg-slate-100 dark:hover:bg-slate-800 ${
|
||||
@@ -122,11 +157,47 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
|
||||
'flex items-center gap-1 truncate text-left',
|
||||
{
|
||||
'font-semibold': field.primaryKey || field.unique,
|
||||
'w-full': editMode,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{field.name}</span>
|
||||
{field.comments ? (
|
||||
{editMode ? (
|
||||
<>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
onBlur={editFieldName}
|
||||
placeholder={field.name}
|
||||
autoFocus
|
||||
type="text"
|
||||
value={fieldName}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) => setFieldName(e.target.value)}
|
||||
className="h-5 w-full border-[0.5px] border-blue-400 bg-slate-100 focus-visible:ring-0 dark:bg-slate-900"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||
onClick={editFieldName}
|
||||
>
|
||||
<Check className="size-4" />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
// <span
|
||||
// className="truncate"
|
||||
// onClick={readonly ? undefined : enterEditMode}
|
||||
// >
|
||||
// {field.name}
|
||||
// </span>
|
||||
<span
|
||||
className="truncate"
|
||||
onDoubleClick={enterEditMode}
|
||||
>
|
||||
{field.name}
|
||||
</span>
|
||||
)}
|
||||
{/* <span className="truncate">{field.name}</span> */}
|
||||
{field.comments && !editMode ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="shrink-0 cursor-pointer text-muted-foreground">
|
||||
@@ -137,42 +208,44 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex max-w-[35%] justify-end gap-1.5 truncate hover:shrink-0">
|
||||
{field.primaryKey ? (
|
||||
{editMode ? null : (
|
||||
<div className="flex max-w-[35%] justify-end gap-1.5 truncate hover:shrink-0">
|
||||
{field.primaryKey ? (
|
||||
<div
|
||||
className={cn(
|
||||
'text-muted-foreground',
|
||||
!readonly ? 'group-hover:hidden' : ''
|
||||
)}
|
||||
>
|
||||
<KeyRound size={14} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'text-muted-foreground',
|
||||
'content-center truncate text-right text-xs text-muted-foreground shrink-0',
|
||||
!readonly ? 'group-hover:hidden' : ''
|
||||
)}
|
||||
>
|
||||
<KeyRound size={14} />
|
||||
{field.type.name}
|
||||
{field.nullable ? '?' : ''}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'content-center truncate text-right text-xs text-muted-foreground shrink-0',
|
||||
!readonly ? 'group-hover:hidden' : ''
|
||||
{readonly ? null : (
|
||||
<div className="hidden flex-row group-hover:flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 p-0 hover:bg-primary-foreground"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeField(tableNodeId, field.id);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="size-3.5 text-red-700" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{field.type.name}
|
||||
{field.nullable ? '?' : ''}
|
||||
</div>
|
||||
{readonly ? null : (
|
||||
<div className="hidden flex-row group-hover:flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 p-0 hover:bg-primary-foreground"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeField(tableNodeId, field.id);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="size-3.5 text-red-700" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Table2,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Check,
|
||||
} from 'lucide-react';
|
||||
import { Label } from '@/components/label/label';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
@@ -22,6 +23,13 @@ import { TableNodeContextMenu } from './table-node-context-menu';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { TableNodeDependencyIndicator } from './table-node-dependency-indicator';
|
||||
import type { EdgeType } from '../canvas';
|
||||
import { Input } from '@/components/input/input';
|
||||
import { useClickAway, useKeyPressEvent } from 'react-use';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
|
||||
export type TableNodeType = Node<
|
||||
{
|
||||
@@ -49,6 +57,9 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
||||
const { openTableFromSidebar, selectSidebarSection } = useLayout();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [tableName, setTableName] = useState(table.name);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
const selectedRelEdges = edges.filter(
|
||||
(edge) =>
|
||||
@@ -125,6 +136,28 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
||||
].sort((a, b) => table.fields.indexOf(a) - table.fields.indexOf(b));
|
||||
}, [expanded, table.fields, isMustDisplayedField]);
|
||||
|
||||
const editTableName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (tableName.trim()) {
|
||||
updateTable(table.id, { name: tableName.trim() });
|
||||
}
|
||||
setEditMode(false);
|
||||
}, [tableName, table.id, updateTable, editMode]);
|
||||
|
||||
const abortEdit = useCallback(() => {
|
||||
setEditMode(false);
|
||||
setTableName(table.name);
|
||||
}, [table.name]);
|
||||
|
||||
useClickAway(inputRef, editTableName);
|
||||
useKeyPressEvent('Enter', editTableName);
|
||||
useKeyPressEvent('Escape', abortEdit);
|
||||
|
||||
const enterEditMode = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableNodeContextMenu table={table}>
|
||||
<div
|
||||
@@ -168,12 +201,47 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
||||
<div className="group flex h-9 items-center justify-between bg-slate-200 px-2 dark:bg-slate-900">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
<Table2 className="size-3.5 shrink-0 text-gray-600 dark:text-primary" />
|
||||
<Label className="truncate text-sm font-bold">
|
||||
{table.name}
|
||||
</Label>
|
||||
{editMode ? (
|
||||
<>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
onBlur={editTableName}
|
||||
placeholder={table.name}
|
||||
autoFocus
|
||||
type="text"
|
||||
value={tableName}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) =>
|
||||
setTableName(e.target.value)
|
||||
}
|
||||
className="h-6 w-full border-[0.5px] border-blue-400 bg-slate-100 focus-visible:ring-0 dark:bg-slate-900"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||
onClick={editTableName}
|
||||
>
|
||||
<Check className="size-4" />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Label
|
||||
className="text-editable truncate px-2 py-0.5 text-sm font-bold"
|
||||
onDoubleClick={enterEditMode}
|
||||
>
|
||||
{table.name}
|
||||
</Label>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t('tool_tips.double_click_to_edit')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="hidden shrink-0 flex-row group-hover:flex">
|
||||
{readonly ? null : (
|
||||
{readonly || editMode ? null : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||
@@ -182,21 +250,23 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
||||
<Pencil className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||
onClick={
|
||||
table.width !== MAX_TABLE_SIZE
|
||||
? expandTable
|
||||
: shrinkTable
|
||||
}
|
||||
>
|
||||
{table.width !== MAX_TABLE_SIZE ? (
|
||||
<ChevronsLeftRight className="size-4" />
|
||||
) : (
|
||||
<ChevronsRightLeft className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
{editMode ? null : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||
onClick={
|
||||
table.width !== MAX_TABLE_SIZE
|
||||
? expandTable
|
||||
: shrinkTable
|
||||
}
|
||||
>
|
||||
{table.width !== MAX_TABLE_SIZE ? (
|
||||
<ChevronsLeftRight className="size-4" />
|
||||
) : (
|
||||
<ChevronsRightLeft className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
+6
@@ -73,8 +73,14 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
||||
setEditMode(false);
|
||||
}, [tableName, table.id, updateTable, editMode]);
|
||||
|
||||
const abortEdit = useCallback(() => {
|
||||
setEditMode(false);
|
||||
setTableName(table.name);
|
||||
}, [table.name]);
|
||||
|
||||
useClickAway(inputRef, editTableName);
|
||||
useKeyPressEvent('Enter', editTableName);
|
||||
useKeyPressEvent('Escape', abortEdit);
|
||||
|
||||
const enterEditMode = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
Reference in New Issue
Block a user