mirror of
				https://github.com/chartdb/chartdb.git
				synced 2025-11-04 05:53:15 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			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