mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-26 17:43:59 +00:00
feat: add zoom navigation buttons to canvas filter for tables and areas (#903)
* feat: add zoom navigation buttons to canvas filter for tables and areas * fix * fix --------- Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
This commit is contained in:
142
src/hooks/use-focus-on.ts
Normal file
142
src/hooks/use-focus-on.ts
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useReactFlow } from '@xyflow/react';
|
||||||
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
|
|
||||||
|
interface FocusOptions {
|
||||||
|
select?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFocusOn = () => {
|
||||||
|
const { fitView, setNodes, setEdges } = useReactFlow();
|
||||||
|
const { hideSidePanel } = useLayout();
|
||||||
|
const { isMd: isDesktop } = useBreakpoint('md');
|
||||||
|
|
||||||
|
const focusOnArea = useCallback(
|
||||||
|
(areaId: string, options: FocusOptions = {}) => {
|
||||||
|
const { select = true } = options;
|
||||||
|
|
||||||
|
if (select) {
|
||||||
|
setNodes((nodes) =>
|
||||||
|
nodes.map((node) =>
|
||||||
|
node.id === areaId
|
||||||
|
? {
|
||||||
|
...node,
|
||||||
|
selected: true,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...node,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fitView({
|
||||||
|
duration: 500,
|
||||||
|
maxZoom: 1,
|
||||||
|
minZoom: 1,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: areaId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDesktop) {
|
||||||
|
hideSidePanel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fitView, setNodes, hideSidePanel, isDesktop]
|
||||||
|
);
|
||||||
|
|
||||||
|
const focusOnTable = useCallback(
|
||||||
|
(tableId: string, options: FocusOptions = {}) => {
|
||||||
|
const { select = true } = options;
|
||||||
|
|
||||||
|
if (select) {
|
||||||
|
setNodes((nodes) =>
|
||||||
|
nodes.map((node) =>
|
||||||
|
node.id === tableId
|
||||||
|
? {
|
||||||
|
...node,
|
||||||
|
selected: true,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...node,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fitView({
|
||||||
|
duration: 500,
|
||||||
|
maxZoom: 1,
|
||||||
|
minZoom: 1,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: tableId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDesktop) {
|
||||||
|
hideSidePanel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fitView, setNodes, hideSidePanel, isDesktop]
|
||||||
|
);
|
||||||
|
|
||||||
|
const focusOnRelationship = useCallback(
|
||||||
|
(
|
||||||
|
relationshipId: string,
|
||||||
|
sourceTableId: string,
|
||||||
|
targetTableId: string,
|
||||||
|
options: FocusOptions = {}
|
||||||
|
) => {
|
||||||
|
const { select = true } = options;
|
||||||
|
|
||||||
|
if (select) {
|
||||||
|
setEdges((edges) =>
|
||||||
|
edges.map((edge) =>
|
||||||
|
edge.id === relationshipId
|
||||||
|
? {
|
||||||
|
...edge,
|
||||||
|
selected: true,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...edge,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fitView({
|
||||||
|
duration: 500,
|
||||||
|
maxZoom: 1,
|
||||||
|
minZoom: 1,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: sourceTableId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: targetTableId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDesktop) {
|
||||||
|
hideSidePanel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fitView, setEdges, hideSidePanel, isDesktop]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
focusOnArea,
|
||||||
|
focusOnTable,
|
||||||
|
focusOnRelationship,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -47,7 +47,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
addTablesToFilter,
|
addTablesToFilter,
|
||||||
removeTablesFromFilter,
|
removeTablesFromFilter,
|
||||||
} = useDiagramFilter();
|
} = useDiagramFilter();
|
||||||
const { fitView, setNodes } = useReactFlow();
|
const { setNodes } = useReactFlow();
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
||||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||||
@@ -160,39 +160,53 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const focusOnTable = useCallback(
|
const selectTable = useCallback(
|
||||||
(tableId: string) => {
|
(tableId: string) => {
|
||||||
// Make sure the table is visible
|
// Make sure the table is visible, selected and trigger animation
|
||||||
setNodes((nodes) =>
|
setNodes((nodes) =>
|
||||||
nodes.map((node) =>
|
nodes.map((node) => {
|
||||||
node.id === tableId
|
if (node.id === tableId) {
|
||||||
? {
|
return {
|
||||||
...node,
|
...node,
|
||||||
hidden: false,
|
|
||||||
selected: true,
|
selected: true,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
highlightTable: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
: {
|
|
||||||
|
return {
|
||||||
...node,
|
...node,
|
||||||
selected: false,
|
selected: false,
|
||||||
}
|
data: {
|
||||||
)
|
...node.data,
|
||||||
|
highlightTable: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Focus on the table
|
// Remove the highlight flag after animation completes
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitView({
|
setNodes((nodes) =>
|
||||||
duration: 500,
|
nodes.map((node) => {
|
||||||
maxZoom: 1,
|
if (node.id === tableId) {
|
||||||
minZoom: 1,
|
return {
|
||||||
nodes: [
|
...node,
|
||||||
{
|
data: {
|
||||||
id: tableId,
|
...node.data,
|
||||||
|
highlightTable: false,
|
||||||
},
|
},
|
||||||
],
|
};
|
||||||
});
|
}
|
||||||
}, 100);
|
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, 600);
|
||||||
},
|
},
|
||||||
[fitView, setNodes]
|
[setNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle node click
|
// Handle node click
|
||||||
@@ -202,13 +216,13 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
const context = node.context as TableContext;
|
const context = node.context as TableContext;
|
||||||
const isTableVisible = context.visible;
|
const isTableVisible = context.visible;
|
||||||
|
|
||||||
// Only focus if table is visible
|
// Only select if table is visible
|
||||||
if (isTableVisible) {
|
if (isTableVisible) {
|
||||||
focusOnTable(node.id);
|
selectTable(node.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[focusOnTable]
|
[selectTable]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Animate in on mount and focus search input
|
// Animate in on mount and focus search input
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Eye, EyeOff } from 'lucide-react';
|
import { Eye, EyeOff, CircleDotDashed } from 'lucide-react';
|
||||||
import { Button } from '@/components/button/button';
|
import { Button } from '@/components/button/button';
|
||||||
import type { TreeNode } from '@/components/tree-view/tree';
|
import type { TreeNode } from '@/components/tree-view/tree';
|
||||||
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
||||||
|
import { useFocusOn } from '@/hooks/use-focus-on';
|
||||||
import type {
|
import type {
|
||||||
AreaContext,
|
AreaContext,
|
||||||
NodeContext,
|
NodeContext,
|
||||||
@@ -12,6 +13,7 @@ import type {
|
|||||||
TableContext,
|
TableContext,
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { FilterTableInfo } from '@/lib/domain/diagram-filter/diagram-filter';
|
import type { FilterTableInfo } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface FilterItemActionsProps {
|
interface FilterItemActionsProps {
|
||||||
node: TreeNode<NodeType, NodeContext>;
|
node: TreeNode<NodeType, NodeContext>;
|
||||||
@@ -40,6 +42,7 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
|||||||
addTablesToFilter,
|
addTablesToFilter,
|
||||||
removeTablesFromFilter,
|
removeTablesFromFilter,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { focusOnArea, focusOnTable } = useFocusOn();
|
||||||
if (node.type === 'schema') {
|
if (node.type === 'schema') {
|
||||||
const context = node.context as SchemaContext;
|
const context = node.context as SchemaContext;
|
||||||
const schemaVisible = context.visible;
|
const schemaVisible = context.visible;
|
||||||
@@ -50,7 +53,7 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="size-7 h-fit p-0"
|
className="h-fit w-6 p-0"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
@@ -67,9 +70,9 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!schemaVisible ? (
|
{!schemaVisible ? (
|
||||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
<EyeOff className="!size-3.5 text-muted-foreground" />
|
||||||
) : (
|
) : (
|
||||||
<Eye className="size-3.5" />
|
<Eye className="!size-3.5" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -81,11 +84,33 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
|||||||
const isUngrouped = context.isUngrouped;
|
const isUngrouped = context.isUngrouped;
|
||||||
const areaId = context.id;
|
const areaId = context.id;
|
||||||
|
|
||||||
|
const handleZoomToArea = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!isUngrouped) {
|
||||||
|
focusOnArea(areaId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="flex h-full items-center gap-0.5">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="size-7 h-fit p-0"
|
className={cn(
|
||||||
|
'flex h-fit w-6 items-center justify-center p-0 opacity-0 transition-opacity group-hover:opacity-100',
|
||||||
|
{
|
||||||
|
'!opacity-0': !areaVisible,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onClick={handleZoomToArea}
|
||||||
|
disabled={!areaVisible}
|
||||||
|
>
|
||||||
|
<CircleDotDashed className="!size-3.5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="flex h-fit w-6 items-center justify-center p-0"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// Toggle all tables in this area
|
// Toggle all tables in this area
|
||||||
@@ -107,11 +132,12 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!areaVisible ? (
|
{!areaVisible ? (
|
||||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
<EyeOff className="!size-3.5 text-muted-foreground" />
|
||||||
) : (
|
) : (
|
||||||
<Eye className="size-3.5" />
|
<Eye className="!size-3.5" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,22 +146,43 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
|||||||
const context = node.context as TableContext;
|
const context = node.context as TableContext;
|
||||||
const tableVisible = context.visible;
|
const tableVisible = context.visible;
|
||||||
|
|
||||||
|
const handleZoomToTable = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
focusOnTable(tableId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="flex h-full items-center gap-0.5">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="size-7 h-fit p-0"
|
className={cn(
|
||||||
|
'flex h-fit w-6 items-center justify-center p-0 opacity-0 transition-opacity group-hover:opacity-100',
|
||||||
|
{
|
||||||
|
'!opacity-0': !tableVisible,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onClick={handleZoomToTable}
|
||||||
|
disabled={!tableVisible}
|
||||||
|
>
|
||||||
|
<CircleDotDashed className="!size-3.5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="flex w-6 items-center justify-center p-0"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleTableFilter(tableId);
|
toggleTableFilter(tableId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!tableVisible ? (
|
{!tableVisible ? (
|
||||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
<EyeOff className="!size-3.5 text-muted-foreground" />
|
||||||
) : (
|
) : (
|
||||||
<Eye className="size-3.5" />
|
<Eye className="!size-3.5" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export type TableNodeType = Node<
|
|||||||
isOverlapping: boolean;
|
isOverlapping: boolean;
|
||||||
highlightOverlappingTables?: boolean;
|
highlightOverlappingTables?: boolean;
|
||||||
hasHighlightedCustomType?: boolean;
|
hasHighlightedCustomType?: boolean;
|
||||||
|
highlightTable?: boolean;
|
||||||
},
|
},
|
||||||
'table'
|
'table'
|
||||||
>;
|
>;
|
||||||
@@ -68,6 +69,7 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
|||||||
isOverlapping,
|
isOverlapping,
|
||||||
highlightOverlappingTables,
|
highlightOverlappingTables,
|
||||||
hasHighlightedCustomType,
|
hasHighlightedCustomType,
|
||||||
|
highlightTable,
|
||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
const { updateTable, relationships, readonly } = useChartDB();
|
const { updateTable, relationships, readonly } = useChartDB();
|
||||||
@@ -321,6 +323,9 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
|||||||
hasHighlightedCustomType
|
hasHighlightedCustomType
|
||||||
? 'ring-2 ring-offset-slate-50 dark:ring-offset-slate-900 ring-yellow-500 ring-offset-2 animate-scale'
|
? 'ring-2 ring-offset-slate-50 dark:ring-offset-slate-900 ring-yellow-500 ring-offset-2 animate-scale'
|
||||||
: '',
|
: '',
|
||||||
|
highlightTable
|
||||||
|
? 'ring-2 ring-offset-slate-50 dark:ring-offset-slate-900 ring-blue-500 ring-offset-2 animate-scale-2'
|
||||||
|
: '',
|
||||||
isDiffTableChanged &&
|
isDiffTableChanged &&
|
||||||
!isSummaryOnly &&
|
!isSummaryOnly &&
|
||||||
!isDiffNewTable &&
|
!isDiffNewTable &&
|
||||||
@@ -339,6 +344,7 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
|||||||
isOverlapping,
|
isOverlapping,
|
||||||
highlightOverlappingTables,
|
highlightOverlappingTables,
|
||||||
hasHighlightedCustomType,
|
hasHighlightedCustomType,
|
||||||
|
highlightTable,
|
||||||
isSummaryOnly,
|
isSummaryOnly,
|
||||||
isDiffTableChanged,
|
isDiffTableChanged,
|
||||||
isDiffNewTable,
|
isDiffNewTable,
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ import {
|
|||||||
} from '@/components/dropdown-menu/dropdown-menu';
|
} from '@/components/dropdown-menu/dropdown-menu';
|
||||||
import { ListItemHeaderButton } from '@/pages/editor-page/side-panel/list-item-header-button/list-item-header-button';
|
import { ListItemHeaderButton } from '@/pages/editor-page/side-panel/list-item-header-button/list-item-header-button';
|
||||||
import { mergeRefs } from '@/lib/utils';
|
import { mergeRefs } from '@/lib/utils';
|
||||||
import { useReactFlow } from '@xyflow/react';
|
import { useFocusOn } from '@/hooks/use-focus-on';
|
||||||
import { useLayout } from '@/hooks/use-layout';
|
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
|
||||||
|
|
||||||
export interface AreaListItemProps {
|
export interface AreaListItemProps {
|
||||||
area: Area;
|
area: Area;
|
||||||
@@ -43,9 +41,7 @@ export const AreaListItem = React.forwardRef<HTMLDivElement, AreaListItemProps>(
|
|||||||
({ area }, forwardedRef) => {
|
({ area }, forwardedRef) => {
|
||||||
const { updateArea, removeArea, readonly } = useChartDB();
|
const { updateArea, removeArea, readonly } = useChartDB();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { fitView, setNodes } = useReactFlow();
|
const { focusOnArea } = useFocusOn();
|
||||||
const { hideSidePanel } = useLayout();
|
|
||||||
const { isMd: isDesktop } = useBreakpoint('md');
|
|
||||||
const [editMode, setEditMode] = React.useState(false);
|
const [editMode, setEditMode] = React.useState(false);
|
||||||
const [areaName, setAreaName] = React.useState(area.name);
|
const [areaName, setAreaName] = React.useState(area.name);
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
@@ -92,38 +88,12 @@ export const AreaListItem = React.forwardRef<HTMLDivElement, AreaListItemProps>(
|
|||||||
[area.id, updateArea]
|
[area.id, updateArea]
|
||||||
);
|
);
|
||||||
|
|
||||||
const focusOnArea = useCallback(
|
const handleFocusOnArea = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setNodes((nodes) =>
|
focusOnArea(area.id);
|
||||||
nodes.map((node) =>
|
|
||||||
node.id == area.id
|
|
||||||
? {
|
|
||||||
...node,
|
|
||||||
selected: true,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
...node,
|
|
||||||
selected: false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
fitView({
|
|
||||||
duration: 500,
|
|
||||||
maxZoom: 1,
|
|
||||||
minZoom: 1,
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: area.id,
|
|
||||||
},
|
},
|
||||||
],
|
[focusOnArea, area.id]
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDesktop) {
|
|
||||||
hideSidePanel();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[fitView, area.id, setNodes, hideSidePanel, isDesktop]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useClickAway(inputRef, saveAreaName);
|
useClickAway(inputRef, saveAreaName);
|
||||||
@@ -241,7 +211,9 @@ export const AreaListItem = React.forwardRef<HTMLDivElement, AreaListItemProps>(
|
|||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
/>
|
/>
|
||||||
<div className="hidden md:group-hover:flex">
|
<div className="hidden md:group-hover:flex">
|
||||||
<ListItemHeaderButton onClick={focusOnArea}>
|
<ListItemHeaderButton
|
||||||
|
onClick={handleFocusOnArea}
|
||||||
|
>
|
||||||
<CircleDotDashed />
|
<CircleDotDashed />
|
||||||
</ListItemHeaderButton>
|
</ListItemHeaderButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ListItemHeaderButton } from '../../../../list-item-header-button/list-i
|
|||||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
import { useReactFlow } from '@xyflow/react';
|
import { useReactFlow } from '@xyflow/react';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import { useFocusOn } from '@/hooks/use-focus-on';
|
||||||
import { useClickAway, useKeyPressEvent } from 'react-use';
|
import { useClickAway, useKeyPressEvent } from 'react-use';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -21,8 +22,6 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/dropdown-menu/dropdown-menu';
|
} from '@/components/dropdown-menu/dropdown-menu';
|
||||||
import { Input } from '@/components/input/input';
|
import { Input } from '@/components/input/input';
|
||||||
import { useLayout } from '@/hooks/use-layout';
|
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface RelationshipListItemHeaderProps {
|
export interface RelationshipListItemHeaderProps {
|
||||||
@@ -33,11 +32,10 @@ export const RelationshipListItemHeader: React.FC<
|
|||||||
RelationshipListItemHeaderProps
|
RelationshipListItemHeaderProps
|
||||||
> = ({ relationship }) => {
|
> = ({ relationship }) => {
|
||||||
const { updateRelationship, removeRelationship, readonly } = useChartDB();
|
const { updateRelationship, removeRelationship, readonly } = useChartDB();
|
||||||
const { fitView, deleteElements, setEdges } = useReactFlow();
|
const { deleteElements } = useReactFlow();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { hideSidePanel } = useLayout();
|
const { focusOnRelationship } = useFocusOn();
|
||||||
const [editMode, setEditMode] = React.useState(false);
|
const [editMode, setEditMode] = React.useState(false);
|
||||||
const { isMd: isDesktop } = useBreakpoint('md');
|
|
||||||
const [relationshipName, setRelationshipName] = React.useState(
|
const [relationshipName, setRelationshipName] = React.useState(
|
||||||
relationship.name
|
relationship.name
|
||||||
);
|
);
|
||||||
@@ -70,48 +68,20 @@ export const RelationshipListItemHeader: React.FC<
|
|||||||
setEditMode(true);
|
setEditMode(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const focusOnRelationship = useCallback(
|
const handleFocusOnRelationship = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setEdges((edges) =>
|
focusOnRelationship(
|
||||||
edges.map((edge) =>
|
relationship.id,
|
||||||
edge.id == relationship.id
|
relationship.sourceTableId,
|
||||||
? {
|
relationship.targetTableId
|
||||||
...edge,
|
|
||||||
selected: true,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
...edge,
|
|
||||||
selected: false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
fitView({
|
|
||||||
duration: 500,
|
|
||||||
maxZoom: 1,
|
|
||||||
minZoom: 1,
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: relationship.sourceTableId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: relationship.targetTableId,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDesktop) {
|
|
||||||
hideSidePanel();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
fitView,
|
focusOnRelationship,
|
||||||
|
relationship.id,
|
||||||
relationship.sourceTableId,
|
relationship.sourceTableId,
|
||||||
relationship.targetTableId,
|
relationship.targetTableId,
|
||||||
setEdges,
|
|
||||||
relationship.id,
|
|
||||||
isDesktop,
|
|
||||||
hideSidePanel,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -182,7 +152,9 @@ export const RelationshipListItemHeader: React.FC<
|
|||||||
<Pencil />
|
<Pencil />
|
||||||
</ListItemHeaderButton>
|
</ListItemHeaderButton>
|
||||||
) : null}
|
) : null}
|
||||||
<ListItemHeaderButton onClick={focusOnRelationship}>
|
<ListItemHeaderButton
|
||||||
|
onClick={handleFocusOnRelationship}
|
||||||
|
>
|
||||||
<CircleDotDashed />
|
<CircleDotDashed />
|
||||||
</ListItemHeaderButton>
|
</ListItemHeaderButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/dropdown-menu/dropdown-menu';
|
} from '@/components/dropdown-menu/dropdown-menu';
|
||||||
import { useReactFlow } from '@xyflow/react';
|
import { useFocusOn } from '@/hooks/use-focus-on';
|
||||||
import { useLayout } from '@/hooks/use-layout';
|
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
import {
|
import {
|
||||||
@@ -62,11 +60,9 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
|||||||
const { schemasDisplayed } = useDiagramFilter();
|
const { schemasDisplayed } = useDiagramFilter();
|
||||||
const { openTableSchemaDialog } = useDialog();
|
const { openTableSchemaDialog } = useDialog();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { fitView, setNodes } = useReactFlow();
|
const { focusOnTable } = useFocusOn();
|
||||||
const { hideSidePanel } = useLayout();
|
|
||||||
const [editMode, setEditMode] = React.useState(false);
|
const [editMode, setEditMode] = React.useState(false);
|
||||||
const [tableName, setTableName] = React.useState(table.name);
|
const [tableName, setTableName] = React.useState(table.name);
|
||||||
const { isMd: isDesktop } = useBreakpoint('md');
|
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
const { listeners } = useSortable({ id: table.id });
|
const { listeners } = useSortable({ id: table.id });
|
||||||
|
|
||||||
@@ -93,38 +89,12 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
|||||||
setEditMode(true);
|
setEditMode(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const focusOnTable = useCallback(
|
const handleFocusOnTable = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setNodes((nodes) =>
|
focusOnTable(table.id);
|
||||||
nodes.map((node) =>
|
|
||||||
node.id == table.id
|
|
||||||
? {
|
|
||||||
...node,
|
|
||||||
selected: true,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
...node,
|
|
||||||
selected: false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
fitView({
|
|
||||||
duration: 500,
|
|
||||||
maxZoom: 1,
|
|
||||||
minZoom: 1,
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: table.id,
|
|
||||||
},
|
},
|
||||||
],
|
[focusOnTable, table.id]
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDesktop) {
|
|
||||||
hideSidePanel();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[fitView, table.id, setNodes, hideSidePanel, isDesktop]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteTableHandler = useCallback(() => {
|
const deleteTableHandler = useCallback(() => {
|
||||||
@@ -339,7 +309,7 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
|||||||
<Pencil />
|
<Pencil />
|
||||||
</ListItemHeaderButton>
|
</ListItemHeaderButton>
|
||||||
) : null}
|
) : null}
|
||||||
<ListItemHeaderButton onClick={focusOnTable}>
|
<ListItemHeaderButton onClick={handleFocusOnTable}>
|
||||||
<CircleDotDashed />
|
<CircleDotDashed />
|
||||||
</ListItemHeaderButton>
|
</ListItemHeaderButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user