mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-23 07:11:56 +00:00
Compare commits
1 Commits
ccb29e0a57
...
jf/nevigat
Author | SHA1 | Date | |
---|---|---|---|
|
34d6b0e525 |
@@ -47,7 +47,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
addTablesToFilter,
|
||||
removeTablesFromFilter,
|
||||
} = useDiagramFilter();
|
||||
const { fitView, setNodes } = useReactFlow();
|
||||
const { setNodes } = useReactFlow();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||
@@ -160,9 +160,9 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
]
|
||||
);
|
||||
|
||||
const focusOnTable = useCallback(
|
||||
const selectTable = useCallback(
|
||||
(tableId: string) => {
|
||||
// Make sure the table is visible
|
||||
// Make sure the table is visible, selected and trigger animation
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) =>
|
||||
node.id === tableId
|
||||
@@ -170,29 +170,40 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
...node,
|
||||
hidden: false,
|
||||
selected: true,
|
||||
data: {
|
||||
...node.data,
|
||||
highlightTable: true,
|
||||
},
|
||||
}
|
||||
: {
|
||||
...node,
|
||||
selected: false,
|
||||
data: {
|
||||
...node.data,
|
||||
highlightTable: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Focus on the table
|
||||
// Remove the highlight flag after animation completes
|
||||
setTimeout(() => {
|
||||
fitView({
|
||||
duration: 500,
|
||||
maxZoom: 1,
|
||||
minZoom: 1,
|
||||
nodes: [
|
||||
{
|
||||
id: tableId,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, 100);
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) =>
|
||||
node.id === tableId
|
||||
? {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
highlightTable: false,
|
||||
},
|
||||
}
|
||||
: node
|
||||
)
|
||||
);
|
||||
}, 600);
|
||||
},
|
||||
[fitView, setNodes]
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
// Handle node click
|
||||
@@ -202,13 +213,13 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
const context = node.context as TableContext;
|
||||
const isTableVisible = context.visible;
|
||||
|
||||
// Only focus if table is visible
|
||||
// Only select if table is visible
|
||||
if (isTableVisible) {
|
||||
focusOnTable(node.id);
|
||||
selectTable(node.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
[focusOnTable]
|
||||
[selectTable]
|
||||
);
|
||||
|
||||
// Animate in on mount and focus search input
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Eye, EyeOff } from 'lucide-react';
|
||||
import { Eye, EyeOff, CircleDotDashed } from 'lucide-react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import type { TreeNode } from '@/components/tree-view/tree';
|
||||
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
import type {
|
||||
AreaContext,
|
||||
NodeContext,
|
||||
@@ -40,6 +41,7 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
||||
addTablesToFilter,
|
||||
removeTablesFromFilter,
|
||||
}) => {
|
||||
const { fitView, setNodes } = useReactFlow();
|
||||
if (node.type === 'schema') {
|
||||
const context = node.context as SchemaContext;
|
||||
const schemaVisible = context.visible;
|
||||
@@ -81,37 +83,105 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
||||
const isUngrouped = context.isUngrouped;
|
||||
const areaId = context.id;
|
||||
|
||||
const handleZoomToArea = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Get all table nodes in this area
|
||||
const tableNodes = isUngrouped
|
||||
? document.querySelectorAll('[data-id]:not([data-area-id])')
|
||||
: document.querySelectorAll(`[data-area-id="${areaId}"]`);
|
||||
|
||||
const nodeIds: string[] = [];
|
||||
tableNodes.forEach((node) => {
|
||||
const nodeId = node.getAttribute('data-id');
|
||||
if (nodeId) nodeIds.push(nodeId);
|
||||
});
|
||||
|
||||
// Make sure the tables in the area are visible
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
const shouldHighlight = isUngrouped
|
||||
? node.type === 'table' &&
|
||||
!(node.data as { table?: { parentAreaId?: string } })
|
||||
?.table?.parentAreaId
|
||||
: node.type === 'area'
|
||||
? node.id === areaId
|
||||
: (node.data as { table?: { parentAreaId?: string } })
|
||||
?.table?.parentAreaId === areaId;
|
||||
|
||||
return {
|
||||
...node,
|
||||
hidden: shouldHighlight ? false : node.hidden,
|
||||
selected: false,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Focus on the area or its tables
|
||||
setTimeout(() => {
|
||||
if (!isUngrouped) {
|
||||
// Zoom to the area node itself
|
||||
fitView({
|
||||
duration: 500,
|
||||
maxZoom: 0.6,
|
||||
minZoom: 0.3,
|
||||
nodes: [{ id: areaId }],
|
||||
padding: 0.2,
|
||||
});
|
||||
} else {
|
||||
// Zoom to all ungrouped tables
|
||||
fitView({
|
||||
duration: 500,
|
||||
maxZoom: 0.6,
|
||||
minZoom: 0.3,
|
||||
padding: 0.2,
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="size-7 h-fit p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// Toggle all tables in this area
|
||||
if (areaVisible) {
|
||||
// Hide all tables in this area
|
||||
removeTablesFromFilter({
|
||||
filterCallback: (table) =>
|
||||
(isUngrouped && !table.areaId) ||
|
||||
(!isUngrouped && table.areaId === areaId),
|
||||
});
|
||||
} else {
|
||||
// Show all tables in this area
|
||||
addTablesToFilter({
|
||||
filterCallback: (table) =>
|
||||
(isUngrouped && !table.areaId) ||
|
||||
(!isUngrouped && table.areaId === areaId),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!areaVisible ? (
|
||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
<div className="flex gap-0.5">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="size-7 h-fit p-0 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
onClick={handleZoomToArea}
|
||||
disabled={!areaVisible}
|
||||
>
|
||||
<CircleDotDashed className="size-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="size-7 h-fit p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// Toggle all tables in this area
|
||||
if (areaVisible) {
|
||||
// Hide all tables in this area
|
||||
removeTablesFromFilter({
|
||||
filterCallback: (table) =>
|
||||
(isUngrouped && !table.areaId) ||
|
||||
(!isUngrouped && table.areaId === areaId),
|
||||
});
|
||||
} else {
|
||||
// Show all tables in this area
|
||||
addTablesToFilter({
|
||||
filterCallback: (table) =>
|
||||
(isUngrouped && !table.areaId) ||
|
||||
(!isUngrouped && table.areaId === areaId),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!areaVisible ? (
|
||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,22 +190,68 @@ export const FilterItemActions: React.FC<FilterItemActionsProps> = ({
|
||||
const context = node.context as TableContext;
|
||||
const tableVisible = context.visible;
|
||||
|
||||
const handleZoomToTable = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Make sure the table is visible and selected
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) =>
|
||||
node.id === tableId
|
||||
? {
|
||||
...node,
|
||||
hidden: false,
|
||||
selected: true,
|
||||
}
|
||||
: {
|
||||
...node,
|
||||
selected: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Focus on the table with less zoom
|
||||
setTimeout(() => {
|
||||
fitView({
|
||||
duration: 500,
|
||||
maxZoom: 0.7,
|
||||
minZoom: 0.5,
|
||||
nodes: [
|
||||
{
|
||||
id: tableId,
|
||||
},
|
||||
],
|
||||
padding: 0.3,
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="size-7 h-fit p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleTableFilter(tableId);
|
||||
}}
|
||||
>
|
||||
{!tableVisible ? (
|
||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
<div className="flex gap-0.5">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="size-7 h-fit p-0 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
onClick={handleZoomToTable}
|
||||
disabled={!tableVisible}
|
||||
>
|
||||
<CircleDotDashed className="size-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="size-7 h-fit p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleTableFilter(tableId);
|
||||
}}
|
||||
>
|
||||
{!tableVisible ? (
|
||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -54,6 +54,7 @@ export type TableNodeType = Node<
|
||||
isOverlapping: boolean;
|
||||
highlightOverlappingTables?: boolean;
|
||||
hasHighlightedCustomType?: boolean;
|
||||
highlightTable?: boolean;
|
||||
},
|
||||
'table'
|
||||
>;
|
||||
@@ -68,6 +69,7 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
||||
isOverlapping,
|
||||
highlightOverlappingTables,
|
||||
hasHighlightedCustomType,
|
||||
highlightTable,
|
||||
},
|
||||
}) => {
|
||||
const { updateTable, relationships, readonly } = useChartDB();
|
||||
@@ -321,6 +323,9 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
||||
hasHighlightedCustomType
|
||||
? '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 &&
|
||||
!isSummaryOnly &&
|
||||
!isDiffNewTable &&
|
||||
@@ -339,6 +344,7 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
||||
isOverlapping,
|
||||
highlightOverlappingTables,
|
||||
hasHighlightedCustomType,
|
||||
highlightTable,
|
||||
isSummaryOnly,
|
||||
isDiffTableChanged,
|
||||
isDiffNewTable,
|
||||
|
Reference in New Issue
Block a user