mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-23 07:11:56 +00:00
fix(filters): refactor diagram filters - remove schema filter (#832)
* refactor(filters): refactor diagram filters * replace old filters * fix storage * fix * fix * fix * fix * fix * fix * fix * fix * fix
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="robots" content="max-image-preview:large" />
|
<meta name="robots" content="max-image-preview:large" />
|
||||||
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
|
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
|
||||||
|
<link rel="canonical" href="https://chartdb.io" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
|
@@ -1,22 +1,23 @@
|
|||||||
import React, { type ReactNode, useCallback, useState } from 'react';
|
import React, { type ReactNode, useCallback, useState } from 'react';
|
||||||
import { canvasContext } from './canvas-context';
|
import { canvasContext } from './canvas-context';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import {
|
import { adjustTablePositions } from '@/lib/domain/db-table';
|
||||||
adjustTablePositions,
|
|
||||||
shouldShowTablesBySchemaFilter,
|
|
||||||
} from '@/lib/domain/db-table';
|
|
||||||
import { useReactFlow } from '@xyflow/react';
|
import { useReactFlow } from '@xyflow/react';
|
||||||
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
|
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
|
||||||
import type { Graph } from '@/lib/graph';
|
import type { Graph } from '@/lib/graph';
|
||||||
import { createGraph } from '@/lib/graph';
|
import { createGraph } from '@/lib/graph';
|
||||||
|
import { useDiagramFilter } from '../diagram-filter-context/use-diagram-filter';
|
||||||
|
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
interface CanvasProviderProps {
|
interface CanvasProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||||
const { tables, relationships, updateTablesState, filteredSchemas } =
|
const { tables, relationships, updateTablesState, databaseType } =
|
||||||
useChartDB();
|
useChartDB();
|
||||||
|
const { filter } = useDiagramFilter();
|
||||||
const { fitView } = useReactFlow();
|
const { fitView } = useReactFlow();
|
||||||
const [overlapGraph, setOverlapGraph] =
|
const [overlapGraph, setOverlapGraph] =
|
||||||
useState<Graph<string>>(createGraph());
|
useState<Graph<string>>(createGraph());
|
||||||
@@ -32,9 +33,18 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
|||||||
const newTables = adjustTablePositions({
|
const newTables = adjustTablePositions({
|
||||||
relationships,
|
relationships,
|
||||||
tables: tables.filter((table) =>
|
tables: tables.filter((table) =>
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
),
|
),
|
||||||
mode: 'all', // Use 'all' mode for manual reordering
|
mode: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedOverlapGraph = findOverlappingTables({
|
const updatedOverlapGraph = findOverlappingTables({
|
||||||
@@ -69,7 +79,14 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
|||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
[filteredSchemas, relationships, tables, updateTablesState, fitView]
|
[
|
||||||
|
filter,
|
||||||
|
relationships,
|
||||||
|
tables,
|
||||||
|
updateTablesState,
|
||||||
|
fitView,
|
||||||
|
databaseType,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -81,9 +81,6 @@ export interface ChartDBContext {
|
|||||||
highlightedCustomType?: DBCustomType;
|
highlightedCustomType?: DBCustomType;
|
||||||
highlightCustomTypeId: (id?: string) => void;
|
highlightCustomTypeId: (id?: string) => void;
|
||||||
|
|
||||||
filteredSchemas?: string[];
|
|
||||||
filterSchemas: (schemaIds: string[]) => void;
|
|
||||||
|
|
||||||
// General operations
|
// General operations
|
||||||
updateDiagramId: (id: string) => Promise<void>;
|
updateDiagramId: (id: string) => Promise<void>;
|
||||||
updateDiagramName: (
|
updateDiagramName: (
|
||||||
@@ -284,11 +281,6 @@ export interface ChartDBContext {
|
|||||||
customType: Partial<DBCustomType>,
|
customType: Partial<DBCustomType>,
|
||||||
options?: { updateHistory: boolean }
|
options?: { updateHistory: boolean }
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
// Filters
|
|
||||||
hiddenTableIds?: string[];
|
|
||||||
addHiddenTableId: (tableId: string) => Promise<void>;
|
|
||||||
removeHiddenTableId: (tableId: string) => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const chartDBContext = createContext<ChartDBContext>({
|
export const chartDBContext = createContext<ChartDBContext>({
|
||||||
@@ -302,8 +294,6 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
customTypes: [],
|
customTypes: [],
|
||||||
schemas: [],
|
schemas: [],
|
||||||
highlightCustomTypeId: emptyFn,
|
highlightCustomTypeId: emptyFn,
|
||||||
filteredSchemas: [],
|
|
||||||
filterSchemas: emptyFn,
|
|
||||||
currentDiagram: {
|
currentDiagram: {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -386,9 +376,4 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
removeCustomType: emptyFn,
|
removeCustomType: emptyFn,
|
||||||
removeCustomTypes: emptyFn,
|
removeCustomTypes: emptyFn,
|
||||||
updateCustomType: emptyFn,
|
updateCustomType: emptyFn,
|
||||||
|
|
||||||
// Filters
|
|
||||||
hiddenTableIds: [],
|
|
||||||
addHiddenTableId: emptyFn,
|
|
||||||
removeHiddenTableId: emptyFn,
|
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import { deepCopy, generateId } from '@/lib/utils';
|
import { deepCopy, generateId } from '@/lib/utils';
|
||||||
import { randomColor } from '@/lib/colors';
|
import { randomColor } from '@/lib/colors';
|
||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
databasesWithSchemas,
|
databasesWithSchemas,
|
||||||
schemaNameToSchemaId,
|
schemaNameToSchemaId,
|
||||||
} from '@/lib/domain/db-schema';
|
} from '@/lib/domain/db-schema';
|
||||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
|
||||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
import { useEventEmitter } from 'ahooks';
|
import { useEventEmitter } from 'ahooks';
|
||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
@@ -29,7 +28,6 @@ import {
|
|||||||
DBCustomTypeKind,
|
DBCustomTypeKind,
|
||||||
type DBCustomType,
|
type DBCustomType,
|
||||||
} from '@/lib/domain/db-custom-type';
|
} from '@/lib/domain/db-custom-type';
|
||||||
import { useConfig } from '@/hooks/use-config';
|
|
||||||
|
|
||||||
export interface ChartDBProviderProps {
|
export interface ChartDBProviderProps {
|
||||||
diagram?: Diagram;
|
diagram?: Diagram;
|
||||||
@@ -43,14 +41,9 @@ export const ChartDBProvider: React.FC<
|
|||||||
const dbStorage = useStorage();
|
const dbStorage = useStorage();
|
||||||
let db = dbStorage;
|
let db = dbStorage;
|
||||||
const events = useEventEmitter<ChartDBEvent>();
|
const events = useEventEmitter<ChartDBEvent>();
|
||||||
const { setSchemasFilter, schemasFilter } = useLocalConfig();
|
|
||||||
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
||||||
useRedoUndoStack();
|
useRedoUndoStack();
|
||||||
const {
|
|
||||||
getHiddenTablesForDiagram,
|
|
||||||
hideTableForDiagram,
|
|
||||||
unhideTableForDiagram,
|
|
||||||
} = useConfig();
|
|
||||||
const [diagramId, setDiagramId] = useState('');
|
const [diagramId, setDiagramId] = useState('');
|
||||||
const [diagramName, setDiagramName] = useState('');
|
const [diagramName, setDiagramName] = useState('');
|
||||||
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
||||||
@@ -72,7 +65,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
|
const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
|
||||||
diagram?.customTypes ?? []
|
diagram?.customTypes ?? []
|
||||||
);
|
);
|
||||||
const [hiddenTableIds, setHiddenTableIds] = useState<string[]>([]);
|
|
||||||
const { events: diffEvents } = useDiff();
|
const { events: diffEvents } = useDiff();
|
||||||
|
|
||||||
const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
|
const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
|
||||||
@@ -96,14 +89,6 @@ export const ChartDBProvider: React.FC<
|
|||||||
|
|
||||||
diffEvents.useSubscription(diffCalculatedHandler);
|
diffEvents.useSubscription(diffCalculatedHandler);
|
||||||
|
|
||||||
// Sync hiddenTableIds with config
|
|
||||||
useEffect(() => {
|
|
||||||
if (diagramId) {
|
|
||||||
const hiddenTables = getHiddenTablesForDiagram(diagramId);
|
|
||||||
setHiddenTableIds(hiddenTables);
|
|
||||||
}
|
|
||||||
}, [diagramId, getHiddenTablesForDiagram]);
|
|
||||||
|
|
||||||
const defaultSchemaName = defaultSchemas[databaseType];
|
const defaultSchemaName = defaultSchemas[databaseType];
|
||||||
|
|
||||||
const readonly = useMemo(
|
const readonly = useMemo(
|
||||||
@@ -141,34 +126,6 @@ export const ChartDBProvider: React.FC<
|
|||||||
[tables, defaultSchemaName, databaseType]
|
[tables, defaultSchemaName, databaseType]
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterSchemas: ChartDBContext['filterSchemas'] = useCallback(
|
|
||||||
(schemaIds) => {
|
|
||||||
setSchemasFilter((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[diagramId]: schemaIds,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[diagramId, setSchemasFilter]
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredSchemas: ChartDBContext['filteredSchemas'] = useMemo(() => {
|
|
||||||
if (schemas.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const schemasFilterFromCache =
|
|
||||||
(schemasFilter[diagramId] ?? []).length === 0
|
|
||||||
? undefined // in case of empty filter, skip cache
|
|
||||||
: schemasFilter[diagramId];
|
|
||||||
|
|
||||||
return (
|
|
||||||
schemasFilterFromCache ?? [
|
|
||||||
schemas.find((s) => s.name === defaultSchemaName)?.id ??
|
|
||||||
schemas[0]?.id,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}, [schemasFilter, diagramId, schemas, defaultSchemaName]);
|
|
||||||
|
|
||||||
const currentDiagram: Diagram = useMemo(
|
const currentDiagram: Diagram = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
id: diagramId,
|
id: diagramId,
|
||||||
@@ -1125,12 +1082,15 @@ export const ChartDBProvider: React.FC<
|
|||||||
|
|
||||||
const sourceFieldName = sourceField?.name ?? '';
|
const sourceFieldName = sourceField?.name ?? '';
|
||||||
|
|
||||||
|
const targetTable = getTable(targetTableId);
|
||||||
|
const targetTableSchema = targetTable?.schema;
|
||||||
|
|
||||||
const relationship: DBRelationship = {
|
const relationship: DBRelationship = {
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
name: `${sourceTableName}_${sourceFieldName}_fk`,
|
name: `${sourceTableName}_${sourceFieldName}_fk`,
|
||||||
sourceSchema: sourceTable?.schema,
|
sourceSchema: sourceTable?.schema,
|
||||||
sourceTableId,
|
sourceTableId,
|
||||||
targetSchema: sourceTable?.schema,
|
targetSchema: targetTableSchema,
|
||||||
targetTableId,
|
targetTableId,
|
||||||
sourceFieldId,
|
sourceFieldId,
|
||||||
targetFieldId,
|
targetFieldId,
|
||||||
@@ -1759,29 +1719,6 @@ export const ChartDBProvider: React.FC<
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const addHiddenTableId: ChartDBContext['addHiddenTableId'] = useCallback(
|
|
||||||
async (tableId: string) => {
|
|
||||||
if (!hiddenTableIds.includes(tableId)) {
|
|
||||||
setHiddenTableIds((prev) => [...prev, tableId]);
|
|
||||||
await hideTableForDiagram(diagramId, tableId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hiddenTableIds, diagramId, hideTableForDiagram]
|
|
||||||
);
|
|
||||||
|
|
||||||
const removeHiddenTableId: ChartDBContext['removeHiddenTableId'] =
|
|
||||||
useCallback(
|
|
||||||
async (tableId: string) => {
|
|
||||||
if (hiddenTableIds.includes(tableId)) {
|
|
||||||
setHiddenTableIds((prev) =>
|
|
||||||
prev.filter((id) => id !== tableId)
|
|
||||||
);
|
|
||||||
await unhideTableForDiagram(diagramId, tableId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hiddenTableIds, diagramId, unhideTableForDiagram]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<chartDBContext.Provider
|
<chartDBContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -1794,10 +1731,8 @@ export const ChartDBProvider: React.FC<
|
|||||||
areas,
|
areas,
|
||||||
currentDiagram,
|
currentDiagram,
|
||||||
schemas,
|
schemas,
|
||||||
filteredSchemas,
|
|
||||||
events,
|
events,
|
||||||
readonly,
|
readonly,
|
||||||
filterSchemas,
|
|
||||||
updateDiagramData,
|
updateDiagramData,
|
||||||
updateDiagramId,
|
updateDiagramId,
|
||||||
updateDiagramName,
|
updateDiagramName,
|
||||||
@@ -1855,9 +1790,6 @@ export const ChartDBProvider: React.FC<
|
|||||||
removeCustomType,
|
removeCustomType,
|
||||||
removeCustomTypes,
|
removeCustomTypes,
|
||||||
updateCustomType,
|
updateCustomType,
|
||||||
hiddenTableIds,
|
|
||||||
addHiddenTableId,
|
|
||||||
removeHiddenTableId,
|
|
||||||
highlightCustomTypeId,
|
highlightCustomTypeId,
|
||||||
highlightedCustomType,
|
highlightedCustomType,
|
||||||
}}
|
}}
|
||||||
|
@@ -8,23 +8,9 @@ export interface ConfigContext {
|
|||||||
config?: Partial<ChartDBConfig>;
|
config?: Partial<ChartDBConfig>;
|
||||||
updateFn?: (config: ChartDBConfig) => ChartDBConfig;
|
updateFn?: (config: ChartDBConfig) => ChartDBConfig;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
getHiddenTablesForDiagram: (diagramId: string) => string[];
|
|
||||||
setHiddenTablesForDiagram: (
|
|
||||||
diagramId: string,
|
|
||||||
hiddenTableIds: string[]
|
|
||||||
) => Promise<void>;
|
|
||||||
hideTableForDiagram: (diagramId: string, tableId: string) => Promise<void>;
|
|
||||||
unhideTableForDiagram: (
|
|
||||||
diagramId: string,
|
|
||||||
tableId: string
|
|
||||||
) => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfigContext = createContext<ConfigContext>({
|
export const ConfigContext = createContext<ConfigContext>({
|
||||||
config: undefined,
|
config: undefined,
|
||||||
updateConfig: emptyFn,
|
updateConfig: emptyFn,
|
||||||
getHiddenTablesForDiagram: () => [],
|
|
||||||
setHiddenTablesForDiagram: emptyFn,
|
|
||||||
hideTableForDiagram: emptyFn,
|
|
||||||
unhideTableForDiagram: emptyFn,
|
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ConfigContext } from './config-context';
|
import { ConfigContext } from './config-context';
|
||||||
|
|
||||||
import { useStorage } from '@/hooks/use-storage';
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
@@ -8,7 +8,7 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { getConfig, updateConfig: updateDataConfig } = useStorage();
|
const { getConfig, updateConfig: updateDataConfig } = useStorage();
|
||||||
const [config, setConfig] = React.useState<ChartDBConfig | undefined>();
|
const [config, setConfig] = useState<ChartDBConfig | undefined>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
@@ -44,84 +44,11 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHiddenTablesForDiagram = (diagramId: string): string[] => {
|
|
||||||
return config?.hiddenTablesByDiagram?.[diagramId] ?? [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const setHiddenTablesForDiagram = async (
|
|
||||||
diagramId: string,
|
|
||||||
hiddenTableIds: string[]
|
|
||||||
): Promise<void> => {
|
|
||||||
return updateConfig({
|
|
||||||
updateFn: (currentConfig) => ({
|
|
||||||
...currentConfig,
|
|
||||||
hiddenTablesByDiagram: {
|
|
||||||
...currentConfig.hiddenTablesByDiagram,
|
|
||||||
[diagramId]: hiddenTableIds,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideTableForDiagram = async (
|
|
||||||
diagramId: string,
|
|
||||||
tableId: string
|
|
||||||
): Promise<void> => {
|
|
||||||
return updateConfig({
|
|
||||||
updateFn: (currentConfig) => {
|
|
||||||
const currentHiddenTables =
|
|
||||||
currentConfig.hiddenTablesByDiagram?.[diagramId] ?? [];
|
|
||||||
if (currentHiddenTables.includes(tableId)) {
|
|
||||||
return currentConfig; // Already hidden, no change needed
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...currentConfig,
|
|
||||||
hiddenTablesByDiagram: {
|
|
||||||
...currentConfig.hiddenTablesByDiagram,
|
|
||||||
[diagramId]: [...currentHiddenTables, tableId],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const unhideTableForDiagram = async (
|
|
||||||
diagramId: string,
|
|
||||||
tableId: string
|
|
||||||
): Promise<void> => {
|
|
||||||
return updateConfig({
|
|
||||||
updateFn: (currentConfig) => {
|
|
||||||
const currentHiddenTables =
|
|
||||||
currentConfig.hiddenTablesByDiagram?.[diagramId] ?? [];
|
|
||||||
const filteredTables = currentHiddenTables.filter(
|
|
||||||
(id) => id !== tableId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filteredTables.length === currentHiddenTables.length) {
|
|
||||||
return currentConfig; // Not hidden, no change needed
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...currentConfig,
|
|
||||||
hiddenTablesByDiagram: {
|
|
||||||
...currentConfig.hiddenTablesByDiagram,
|
|
||||||
[diagramId]: filteredTables,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigContext.Provider
|
<ConfigContext.Provider
|
||||||
value={{
|
value={{
|
||||||
config,
|
config,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
getHiddenTablesForDiagram,
|
|
||||||
setHiddenTablesForDiagram,
|
|
||||||
hideTableForDiagram,
|
|
||||||
unhideTableForDiagram,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
import type { DBSchema } from '@/lib/domain';
|
||||||
|
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import { emptyFn } from '@/lib/utils';
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export interface DiagramFilterContext {
|
||||||
|
filter?: DiagramFilter;
|
||||||
|
|
||||||
|
hasActiveFilter: boolean;
|
||||||
|
schemasDisplayed: DBSchema[];
|
||||||
|
|
||||||
|
// schemas
|
||||||
|
schemaIdsFilter?: string[];
|
||||||
|
addSchemaIdsFilter: (...ids: string[]) => void;
|
||||||
|
removeSchemaIdsFilter: (...ids: string[]) => void;
|
||||||
|
clearSchemaIdsFilter: () => void;
|
||||||
|
|
||||||
|
// tables
|
||||||
|
tableIdsFilter?: string[];
|
||||||
|
addTableIdsFilter: (...ids: string[]) => void;
|
||||||
|
removeTableIdsFilter: (...ids: string[]) => void;
|
||||||
|
clearTableIdsFilter: () => void;
|
||||||
|
setTableIdsFilterEmpty: () => void;
|
||||||
|
|
||||||
|
// reset
|
||||||
|
resetFilter: () => void;
|
||||||
|
|
||||||
|
// smart filters
|
||||||
|
toggleSchemaFilter: (schemaId: string) => void;
|
||||||
|
toggleTableFilter: (tableId: string) => void;
|
||||||
|
addSchemaIfFiltered: (schemaId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const diagramFilterContext = createContext<DiagramFilterContext>({
|
||||||
|
hasActiveFilter: false,
|
||||||
|
addSchemaIdsFilter: emptyFn,
|
||||||
|
addTableIdsFilter: emptyFn,
|
||||||
|
clearSchemaIdsFilter: emptyFn,
|
||||||
|
clearTableIdsFilter: emptyFn,
|
||||||
|
setTableIdsFilterEmpty: emptyFn,
|
||||||
|
removeSchemaIdsFilter: emptyFn,
|
||||||
|
removeTableIdsFilter: emptyFn,
|
||||||
|
resetFilter: emptyFn,
|
||||||
|
toggleSchemaFilter: emptyFn,
|
||||||
|
toggleTableFilter: emptyFn,
|
||||||
|
addSchemaIfFiltered: emptyFn,
|
||||||
|
schemasDisplayed: [],
|
||||||
|
});
|
442
src/context/diagram-filter-context/diagram-filter-provider.tsx
Normal file
442
src/context/diagram-filter-context/diagram-filter-provider.tsx
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import type { DiagramFilterContext } from './diagram-filter-context';
|
||||||
|
import { diagramFilterContext } from './diagram-filter-context';
|
||||||
|
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import { reduceFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import { filterSchema, filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { schemaNameToSchemaId } from '@/lib/domain';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
|
export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { diagramId, tables, schemas, databaseType } = useChartDB();
|
||||||
|
const { getDiagramFilter, updateDiagramFilter } = useStorage();
|
||||||
|
const [filter, setFilter] = useState<DiagramFilter>({});
|
||||||
|
|
||||||
|
const allSchemasIds = useMemo(() => {
|
||||||
|
return schemas.map((schema) => schema.id);
|
||||||
|
}, [schemas]);
|
||||||
|
|
||||||
|
const allTables = useMemo(() => {
|
||||||
|
return tables.map((table) => ({
|
||||||
|
id: table.id,
|
||||||
|
schemaId: table.schema
|
||||||
|
? schemaNameToSchemaId(table.schema)
|
||||||
|
: defaultSchemas[databaseType],
|
||||||
|
schema: table.schema,
|
||||||
|
}));
|
||||||
|
}, [tables, databaseType]);
|
||||||
|
|
||||||
|
const diagramIdOfLoadedFilter = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (diagramId && diagramId === diagramIdOfLoadedFilter.current) {
|
||||||
|
updateDiagramFilter(diagramId, filter);
|
||||||
|
}
|
||||||
|
}, [diagramId, filter, updateDiagramFilter]);
|
||||||
|
|
||||||
|
// Reset filter when diagram changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (diagramIdOfLoadedFilter.current === diagramId) {
|
||||||
|
// If the diagramId hasn't changed, do not reset the filter
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadFilterFromStorage = async (diagramId: string) => {
|
||||||
|
if (diagramId) {
|
||||||
|
const storedFilter = await getDiagramFilter(diagramId);
|
||||||
|
setFilter(storedFilter ?? {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setFilter({});
|
||||||
|
|
||||||
|
if (diagramId) {
|
||||||
|
loadFilterFromStorage(diagramId);
|
||||||
|
diagramIdOfLoadedFilter.current = diagramId;
|
||||||
|
}
|
||||||
|
}, [diagramId, getDiagramFilter]);
|
||||||
|
|
||||||
|
// Schema methods
|
||||||
|
const addSchemaIds: DiagramFilterContext['addSchemaIdsFilter'] =
|
||||||
|
useCallback((...ids: string[]) => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
schemaIds: [
|
||||||
|
...new Set([...(prev.schemaIds || []), ...ids]),
|
||||||
|
],
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const removeSchemaIds: DiagramFilterContext['removeSchemaIdsFilter'] =
|
||||||
|
useCallback((...ids: string[]) => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
schemaIds: prev.schemaIds?.filter(
|
||||||
|
(id) => !ids.includes(id)
|
||||||
|
),
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clearSchemaIds: DiagramFilterContext['clearSchemaIdsFilter'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
schemaIds: undefined,
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Table methods
|
||||||
|
const addTableIds: DiagramFilterContext['addTableIdsFilter'] = useCallback(
|
||||||
|
(...ids: string[]) => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
tableIds: [
|
||||||
|
...new Set([...(prev.tableIds || []), ...ids]),
|
||||||
|
],
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeTableIds: DiagramFilterContext['removeTableIdsFilter'] =
|
||||||
|
useCallback((...ids: string[]) => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
tableIds: prev.tableIds?.filter(
|
||||||
|
(id) => !ids.includes(id)
|
||||||
|
),
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clearTableIds: DiagramFilterContext['clearTableIdsFilter'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
tableIds: undefined,
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setTableIdsEmpty: DiagramFilterContext['setTableIdsFilterEmpty'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
tableIds: [],
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Reset filter
|
||||||
|
const resetFilter: DiagramFilterContext['resetFilter'] = useCallback(() => {
|
||||||
|
setFilter({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleSchemaFilter: DiagramFilterContext['toggleSchemaFilter'] =
|
||||||
|
useCallback(
|
||||||
|
(schemaId: string) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
const currentSchemaIds = prev.schemaIds;
|
||||||
|
|
||||||
|
// Check if schema is currently visible
|
||||||
|
const isSchemaVisible = filterSchema({
|
||||||
|
schemaId,
|
||||||
|
schemaIdsFilter: currentSchemaIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
let newSchemaIds: string[] | undefined;
|
||||||
|
let newTableIds: string[] | undefined = prev.tableIds;
|
||||||
|
|
||||||
|
if (isSchemaVisible) {
|
||||||
|
// Schema is visible, make it not visible
|
||||||
|
if (!currentSchemaIds) {
|
||||||
|
// All schemas are visible, create filter with all except this one
|
||||||
|
newSchemaIds = allSchemasIds.filter(
|
||||||
|
(id) => id !== schemaId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Remove this schema from the filter
|
||||||
|
newSchemaIds = currentSchemaIds.filter(
|
||||||
|
(id) => id !== schemaId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove tables from this schema from tableIds if present
|
||||||
|
if (prev.tableIds) {
|
||||||
|
const schemaTableIds = allTables
|
||||||
|
.filter((table) => table.schemaId === schemaId)
|
||||||
|
.map((table) => table.id);
|
||||||
|
newTableIds = prev.tableIds.filter(
|
||||||
|
(id) => !schemaTableIds.includes(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Schema is not visible, make it visible
|
||||||
|
newSchemaIds = [
|
||||||
|
...new Set([...(currentSchemaIds || []), schemaId]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add tables from this schema to tableIds if tableIds is defined
|
||||||
|
if (prev.tableIds) {
|
||||||
|
const schemaTableIds = allTables
|
||||||
|
.filter((table) => table.schemaId === schemaId)
|
||||||
|
.map((table) => table.id);
|
||||||
|
newTableIds = [
|
||||||
|
...new Set([
|
||||||
|
...prev.tableIds,
|
||||||
|
...schemaTableIds,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reduceFilter to optimize and handle edge cases
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
schemaIds: newSchemaIds,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allSchemasIds, allTables]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleTableFilterForNoSchema = useCallback(
|
||||||
|
(tableId: string) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
const currentTableIds = prev.tableIds;
|
||||||
|
|
||||||
|
// Check if table is currently visible
|
||||||
|
const isTableVisible = filterTable({
|
||||||
|
table: { id: tableId, schema: undefined },
|
||||||
|
filter: prev,
|
||||||
|
options: { defaultSchema: undefined },
|
||||||
|
});
|
||||||
|
|
||||||
|
let newTableIds: string[] | undefined;
|
||||||
|
|
||||||
|
if (isTableVisible) {
|
||||||
|
// Table is visible, make it not visible
|
||||||
|
if (!currentTableIds) {
|
||||||
|
// All tables are visible, create filter with all except this one
|
||||||
|
newTableIds = allTables
|
||||||
|
.filter((t) => t.id !== tableId)
|
||||||
|
.map((t) => t.id);
|
||||||
|
} else {
|
||||||
|
// Remove this table from the filter
|
||||||
|
newTableIds = currentTableIds.filter(
|
||||||
|
(id) => id !== tableId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Table is not visible, make it visible
|
||||||
|
newTableIds = [
|
||||||
|
...new Set([...(currentTableIds || []), tableId]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reduceFilter to optimize and handle edge cases
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
schemaIds: undefined,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allTables]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleTableFilter: DiagramFilterContext['toggleTableFilter'] =
|
||||||
|
useCallback(
|
||||||
|
(tableId: string) => {
|
||||||
|
if (!defaultSchemas[databaseType]) {
|
||||||
|
// No schemas, toggle table filter without schema context
|
||||||
|
toggleTableFilterForNoSchema(tableId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter((prev) => {
|
||||||
|
// Find the table in the tables list
|
||||||
|
const tableInfo = allTables.find((t) => t.id === tableId);
|
||||||
|
|
||||||
|
if (!tableInfo) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if table is currently visible using filterTable
|
||||||
|
const isTableVisible = filterTable({
|
||||||
|
table: {
|
||||||
|
id: tableInfo.id,
|
||||||
|
schema: tableInfo.schema,
|
||||||
|
},
|
||||||
|
filter: prev,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let newSchemaIds = prev.schemaIds;
|
||||||
|
let newTableIds = prev.tableIds;
|
||||||
|
|
||||||
|
if (isTableVisible) {
|
||||||
|
// Table is visible, make it not visible
|
||||||
|
|
||||||
|
// If the table is visible due to its schema being in schemaIds
|
||||||
|
if (
|
||||||
|
tableInfo?.schemaId &&
|
||||||
|
prev.schemaIds?.includes(tableInfo.schemaId)
|
||||||
|
) {
|
||||||
|
// Remove the schema from schemaIds and add all other tables from that schema to tableIds
|
||||||
|
newSchemaIds = prev.schemaIds.filter(
|
||||||
|
(id) => id !== tableInfo.schemaId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get all other tables from this schema (except the one being toggled)
|
||||||
|
const otherTablesFromSchema = allTables
|
||||||
|
.filter(
|
||||||
|
(t) =>
|
||||||
|
t.schemaId === tableInfo.schemaId &&
|
||||||
|
t.id !== tableId
|
||||||
|
)
|
||||||
|
.map((t) => t.id);
|
||||||
|
|
||||||
|
// Add these tables to tableIds
|
||||||
|
newTableIds = [
|
||||||
|
...(prev.tableIds || []),
|
||||||
|
...otherTablesFromSchema,
|
||||||
|
];
|
||||||
|
} else if (prev.tableIds?.includes(tableId)) {
|
||||||
|
// Table is visible because it's in tableIds, remove it
|
||||||
|
newTableIds = prev.tableIds.filter(
|
||||||
|
(id) => id !== tableId
|
||||||
|
);
|
||||||
|
} else if (!prev.tableIds && !prev.schemaIds) {
|
||||||
|
// No filters = all visible, create filter with all tables except this one
|
||||||
|
newTableIds = allTables
|
||||||
|
.filter((t) => t.id !== tableId)
|
||||||
|
.map((t) => t.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Table is not visible, make it visible by adding to tableIds
|
||||||
|
newTableIds = [...(prev.tableIds || []), tableId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reduceFilter to optimize and handle edge cases
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
schemaIds: newSchemaIds,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allTables, databaseType, toggleTableFilterForNoSchema]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addSchemaIfFiltered: DiagramFilterContext['addSchemaIfFiltered'] =
|
||||||
|
useCallback(
|
||||||
|
(schemaId: string) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
const currentSchemaIds = prev.schemaIds;
|
||||||
|
if (!currentSchemaIds) {
|
||||||
|
// No schemas are filtered
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If schema is already filtered, do nothing
|
||||||
|
if (currentSchemaIds.includes(schemaId)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add schema to the filter
|
||||||
|
const newSchemaIds = [...currentSchemaIds, schemaId];
|
||||||
|
|
||||||
|
if (newSchemaIds.length === allSchemasIds.length) {
|
||||||
|
// All schemas are now filtered, set to undefined
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
schemaIds: undefined,
|
||||||
|
} satisfies DiagramFilter;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
schemaIds: newSchemaIds,
|
||||||
|
} satisfies DiagramFilter;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allSchemasIds.length]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasActiveFilter: boolean = useMemo(() => {
|
||||||
|
return !!filter.schemaIds || !!filter.tableIds;
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
|
const schemasDisplayed: DiagramFilterContext['schemasDisplayed'] =
|
||||||
|
useMemo(() => {
|
||||||
|
if (!filter.schemaIds) {
|
||||||
|
return schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
return schemas.filter((schema) =>
|
||||||
|
filter.schemaIds?.includes(schema.id)
|
||||||
|
);
|
||||||
|
}, [filter.schemaIds, schemas]);
|
||||||
|
|
||||||
|
const value: DiagramFilterContext = {
|
||||||
|
filter,
|
||||||
|
schemaIdsFilter: filter.schemaIds,
|
||||||
|
addSchemaIdsFilter: addSchemaIds,
|
||||||
|
removeSchemaIdsFilter: removeSchemaIds,
|
||||||
|
clearSchemaIdsFilter: clearSchemaIds,
|
||||||
|
setTableIdsFilterEmpty: setTableIdsEmpty,
|
||||||
|
tableIdsFilter: filter.tableIds,
|
||||||
|
addTableIdsFilter: addTableIds,
|
||||||
|
removeTableIdsFilter: removeTableIds,
|
||||||
|
clearTableIdsFilter: clearTableIds,
|
||||||
|
resetFilter,
|
||||||
|
toggleSchemaFilter,
|
||||||
|
toggleTableFilter,
|
||||||
|
addSchemaIfFiltered,
|
||||||
|
hasActiveFilter,
|
||||||
|
schemasDisplayed,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<diagramFilterContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</diagramFilterContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
4
src/context/diagram-filter-context/use-diagram-filter.ts
Normal file
4
src/context/diagram-filter-context/use-diagram-filter.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { diagramFilterContext } from './diagram-filter-context';
|
||||||
|
|
||||||
|
export const useDiagramFilter = () => useContext(diagramFilterContext);
|
@@ -36,10 +36,6 @@ export interface LayoutContext {
|
|||||||
hideSidePanel: () => void;
|
hideSidePanel: () => void;
|
||||||
showSidePanel: () => void;
|
showSidePanel: () => void;
|
||||||
toggleSidePanel: () => void;
|
toggleSidePanel: () => void;
|
||||||
|
|
||||||
isSelectSchemaOpen: boolean;
|
|
||||||
openSelectSchema: () => void;
|
|
||||||
closeSelectSchema: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const layoutContext = createContext<LayoutContext>({
|
export const layoutContext = createContext<LayoutContext>({
|
||||||
@@ -70,8 +66,4 @@ export const layoutContext = createContext<LayoutContext>({
|
|||||||
hideSidePanel: emptyFn,
|
hideSidePanel: emptyFn,
|
||||||
showSidePanel: emptyFn,
|
showSidePanel: emptyFn,
|
||||||
toggleSidePanel: emptyFn,
|
toggleSidePanel: emptyFn,
|
||||||
|
|
||||||
isSelectSchemaOpen: false,
|
|
||||||
openSelectSchema: emptyFn,
|
|
||||||
closeSelectSchema: emptyFn,
|
|
||||||
});
|
});
|
||||||
|
@@ -23,8 +23,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
React.useState<SidebarSection>('tables');
|
React.useState<SidebarSection>('tables');
|
||||||
const [isSidePanelShowed, setIsSidePanelShowed] =
|
const [isSidePanelShowed, setIsSidePanelShowed] =
|
||||||
React.useState<boolean>(isDesktop);
|
React.useState<boolean>(isDesktop);
|
||||||
const [isSelectSchemaOpen, setIsSelectSchemaOpen] =
|
|
||||||
React.useState<boolean>(false);
|
|
||||||
|
|
||||||
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
||||||
() => setOpenedTableInSidebar('');
|
() => setOpenedTableInSidebar('');
|
||||||
@@ -88,11 +86,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setOpenedTableInSidebar(customTypeId);
|
setOpenedTableInSidebar(customTypeId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
|
|
||||||
setIsSelectSchemaOpen(true);
|
|
||||||
|
|
||||||
const closeSelectSchema: LayoutContext['closeSelectSchema'] = () =>
|
|
||||||
setIsSelectSchemaOpen(false);
|
|
||||||
return (
|
return (
|
||||||
<layoutContext.Provider
|
<layoutContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -108,9 +101,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
hideSidePanel,
|
hideSidePanel,
|
||||||
showSidePanel,
|
showSidePanel,
|
||||||
toggleSidePanel,
|
toggleSidePanel,
|
||||||
isSelectSchemaOpen,
|
|
||||||
openSelectSchema,
|
|
||||||
closeSelectSchema,
|
|
||||||
openedDependencyInSidebar,
|
openedDependencyInSidebar,
|
||||||
openDependencyFromSidebar,
|
openDependencyFromSidebar,
|
||||||
closeAllDependenciesInSidebar,
|
closeAllDependenciesInSidebar,
|
||||||
|
@@ -4,8 +4,6 @@ import type { Theme } from '../theme-context/theme-context';
|
|||||||
|
|
||||||
export type ScrollAction = 'pan' | 'zoom';
|
export type ScrollAction = 'pan' | 'zoom';
|
||||||
|
|
||||||
export type SchemasFilter = Record<string, string[]>;
|
|
||||||
|
|
||||||
export interface LocalConfigContext {
|
export interface LocalConfigContext {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
setTheme: (theme: Theme) => void;
|
setTheme: (theme: Theme) => void;
|
||||||
@@ -13,20 +11,12 @@ export interface LocalConfigContext {
|
|||||||
scrollAction: ScrollAction;
|
scrollAction: ScrollAction;
|
||||||
setScrollAction: (action: ScrollAction) => void;
|
setScrollAction: (action: ScrollAction) => void;
|
||||||
|
|
||||||
schemasFilter: SchemasFilter;
|
|
||||||
setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>;
|
|
||||||
|
|
||||||
showCardinality: boolean;
|
showCardinality: boolean;
|
||||||
setShowCardinality: (showCardinality: boolean) => void;
|
setShowCardinality: (showCardinality: boolean) => void;
|
||||||
|
|
||||||
showFieldAttributes: boolean;
|
showFieldAttributes: boolean;
|
||||||
setShowFieldAttributes: (showFieldAttributes: boolean) => void;
|
setShowFieldAttributes: (showFieldAttributes: boolean) => void;
|
||||||
|
|
||||||
hideMultiSchemaNotification: boolean;
|
|
||||||
setHideMultiSchemaNotification: (
|
|
||||||
hideMultiSchemaNotification: boolean
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
githubRepoOpened: boolean;
|
githubRepoOpened: boolean;
|
||||||
setGithubRepoOpened: (githubRepoOpened: boolean) => void;
|
setGithubRepoOpened: (githubRepoOpened: boolean) => void;
|
||||||
|
|
||||||
@@ -47,18 +37,12 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
|||||||
scrollAction: 'pan',
|
scrollAction: 'pan',
|
||||||
setScrollAction: emptyFn,
|
setScrollAction: emptyFn,
|
||||||
|
|
||||||
schemasFilter: {},
|
|
||||||
setSchemasFilter: emptyFn,
|
|
||||||
|
|
||||||
showCardinality: true,
|
showCardinality: true,
|
||||||
setShowCardinality: emptyFn,
|
setShowCardinality: emptyFn,
|
||||||
|
|
||||||
showFieldAttributes: true,
|
showFieldAttributes: true,
|
||||||
setShowFieldAttributes: emptyFn,
|
setShowFieldAttributes: emptyFn,
|
||||||
|
|
||||||
hideMultiSchemaNotification: false,
|
|
||||||
setHideMultiSchemaNotification: emptyFn,
|
|
||||||
|
|
||||||
githubRepoOpened: false,
|
githubRepoOpened: false,
|
||||||
setGithubRepoOpened: emptyFn,
|
setGithubRepoOpened: emptyFn,
|
||||||
|
|
||||||
|
@@ -1,14 +1,12 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import type { SchemasFilter, ScrollAction } from './local-config-context';
|
import type { ScrollAction } from './local-config-context';
|
||||||
import { LocalConfigContext } from './local-config-context';
|
import { LocalConfigContext } from './local-config-context';
|
||||||
import type { Theme } from '../theme-context/theme-context';
|
import type { Theme } from '../theme-context/theme-context';
|
||||||
|
|
||||||
const themeKey = 'theme';
|
const themeKey = 'theme';
|
||||||
const scrollActionKey = 'scroll_action';
|
const scrollActionKey = 'scroll_action';
|
||||||
const schemasFilterKey = 'schemas_filter';
|
|
||||||
const showCardinalityKey = 'show_cardinality';
|
const showCardinalityKey = 'show_cardinality';
|
||||||
const showFieldAttributesKey = 'show_field_attributes';
|
const showFieldAttributesKey = 'show_field_attributes';
|
||||||
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
|
|
||||||
const githubRepoOpenedKey = 'github_repo_opened';
|
const githubRepoOpenedKey = 'github_repo_opened';
|
||||||
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
||||||
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
|
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
|
||||||
@@ -25,12 +23,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
|
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
|
||||||
);
|
);
|
||||||
|
|
||||||
const [schemasFilter, setSchemasFilter] = React.useState<SchemasFilter>(
|
|
||||||
JSON.parse(
|
|
||||||
localStorage.getItem(schemasFilterKey) || '{}'
|
|
||||||
) as SchemasFilter
|
|
||||||
);
|
|
||||||
|
|
||||||
const [showCardinality, setShowCardinality] = React.useState<boolean>(
|
const [showCardinality, setShowCardinality] = React.useState<boolean>(
|
||||||
(localStorage.getItem(showCardinalityKey) || 'true') === 'true'
|
(localStorage.getItem(showCardinalityKey) || 'true') === 'true'
|
||||||
);
|
);
|
||||||
@@ -40,12 +32,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
(localStorage.getItem(showFieldAttributesKey) || 'true') === 'true'
|
(localStorage.getItem(showFieldAttributesKey) || 'true') === 'true'
|
||||||
);
|
);
|
||||||
|
|
||||||
const [hideMultiSchemaNotification, setHideMultiSchemaNotification] =
|
|
||||||
React.useState<boolean>(
|
|
||||||
(localStorage.getItem(hideMultiSchemaNotificationKey) ||
|
|
||||||
'false') === 'true'
|
|
||||||
);
|
|
||||||
|
|
||||||
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
|
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
|
||||||
(localStorage.getItem(githubRepoOpenedKey) || 'false') === 'true'
|
(localStorage.getItem(githubRepoOpenedKey) || 'false') === 'true'
|
||||||
);
|
);
|
||||||
@@ -77,13 +63,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
||||||
}, [githubRepoOpened]);
|
}, [githubRepoOpened]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem(
|
|
||||||
hideMultiSchemaNotificationKey,
|
|
||||||
hideMultiSchemaNotification.toString()
|
|
||||||
);
|
|
||||||
}, [hideMultiSchemaNotification]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(themeKey, theme);
|
localStorage.setItem(themeKey, theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
@@ -92,10 +71,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
localStorage.setItem(scrollActionKey, scrollAction);
|
localStorage.setItem(scrollActionKey, scrollAction);
|
||||||
}, [scrollAction]);
|
}, [scrollAction]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem(schemasFilterKey, JSON.stringify(schemasFilter));
|
|
||||||
}, [schemasFilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(showCardinalityKey, showCardinality.toString());
|
localStorage.setItem(showCardinalityKey, showCardinality.toString());
|
||||||
}, [showCardinality]);
|
}, [showCardinality]);
|
||||||
@@ -121,14 +96,10 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setTheme,
|
setTheme,
|
||||||
scrollAction,
|
scrollAction,
|
||||||
setScrollAction,
|
setScrollAction,
|
||||||
schemasFilter,
|
|
||||||
setSchemasFilter,
|
|
||||||
showCardinality,
|
showCardinality,
|
||||||
setShowCardinality,
|
setShowCardinality,
|
||||||
showFieldAttributes,
|
showFieldAttributes,
|
||||||
setShowFieldAttributes,
|
setShowFieldAttributes,
|
||||||
hideMultiSchemaNotification,
|
|
||||||
setHideMultiSchemaNotification,
|
|
||||||
setGithubRepoOpened,
|
setGithubRepoOpened,
|
||||||
githubRepoOpened,
|
githubRepoOpened,
|
||||||
starUsDialogLastOpen,
|
starUsDialogLastOpen,
|
||||||
|
@@ -7,12 +7,21 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
|||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import type { Area } from '@/lib/domain/area';
|
import type { Area } from '@/lib/domain/area';
|
||||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||||
|
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
|
||||||
export interface StorageContext {
|
export interface StorageContext {
|
||||||
// Config operations
|
// Config operations
|
||||||
getConfig: () => Promise<ChartDBConfig | undefined>;
|
getConfig: () => Promise<ChartDBConfig | undefined>;
|
||||||
updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>;
|
updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>;
|
||||||
|
|
||||||
|
// Diagram filter operations
|
||||||
|
getDiagramFilter: (diagramId: string) => Promise<DiagramFilter | undefined>;
|
||||||
|
updateDiagramFilter: (
|
||||||
|
diagramId: string,
|
||||||
|
filter: DiagramFilter
|
||||||
|
) => Promise<void>;
|
||||||
|
deleteDiagramFilter: (diagramId: string) => Promise<void>;
|
||||||
|
|
||||||
// Diagram operations
|
// Diagram operations
|
||||||
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
||||||
listDiagrams: (options?: {
|
listDiagrams: (options?: {
|
||||||
@@ -132,6 +141,10 @@ export const storageInitialValue: StorageContext = {
|
|||||||
getConfig: emptyFn,
|
getConfig: emptyFn,
|
||||||
updateConfig: emptyFn,
|
updateConfig: emptyFn,
|
||||||
|
|
||||||
|
getDiagramFilter: emptyFn,
|
||||||
|
updateDiagramFilter: emptyFn,
|
||||||
|
deleteDiagramFilter: emptyFn,
|
||||||
|
|
||||||
addDiagram: emptyFn,
|
addDiagram: emptyFn,
|
||||||
listDiagrams: emptyFn,
|
listDiagrams: emptyFn,
|
||||||
getDiagram: emptyFn,
|
getDiagram: emptyFn,
|
||||||
|
@@ -10,6 +10,7 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
|||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import type { Area } from '@/lib/domain/area';
|
import type { Area } from '@/lib/domain/area';
|
||||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||||
|
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
|
||||||
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -44,6 +45,10 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
ChartDBConfig & { id: number },
|
ChartDBConfig & { id: number },
|
||||||
'id' // primary key "id" (for the typings only)
|
'id' // primary key "id" (for the typings only)
|
||||||
>;
|
>;
|
||||||
|
diagram_filters: EntityTable<
|
||||||
|
DiagramFilter & { diagramId: string },
|
||||||
|
'diagramId' // primary key "id" (for the typings only)
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Schema declaration:
|
// Schema declaration:
|
||||||
@@ -190,6 +195,27 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
config: '++id, defaultDiagramId',
|
config: '++id, defaultDiagramId',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dexieDB
|
||||||
|
.version(12)
|
||||||
|
.stores({
|
||||||
|
diagrams:
|
||||||
|
'++id, name, databaseType, databaseEdition, createdAt, updatedAt',
|
||||||
|
db_tables:
|
||||||
|
'++id, diagramId, name, schema, x, y, fields, indexes, color, createdAt, width, comment, isView, isMaterializedView, order',
|
||||||
|
db_relationships:
|
||||||
|
'++id, diagramId, name, sourceSchema, sourceTableId, targetSchema, targetTableId, sourceFieldId, targetFieldId, type, createdAt',
|
||||||
|
db_dependencies:
|
||||||
|
'++id, diagramId, schema, tableId, dependentSchema, dependentTableId, createdAt',
|
||||||
|
areas: '++id, diagramId, name, x, y, width, height, color',
|
||||||
|
db_custom_types:
|
||||||
|
'++id, diagramId, schema, type, kind, values, fields',
|
||||||
|
config: '++id, defaultDiagramId',
|
||||||
|
diagram_filters: 'diagramId, tableIds, schemasIds',
|
||||||
|
})
|
||||||
|
.upgrade((tx) => {
|
||||||
|
tx.table('config').clear();
|
||||||
|
});
|
||||||
|
|
||||||
dexieDB.on('ready', async () => {
|
dexieDB.on('ready', async () => {
|
||||||
const config = await dexieDB.config.get(1);
|
const config = await dexieDB.config.get(1);
|
||||||
|
|
||||||
@@ -217,6 +243,32 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[db]
|
[db]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getDiagramFilter: StorageContext['getDiagramFilter'] = useCallback(
|
||||||
|
async (diagramId: string): Promise<DiagramFilter | undefined> => {
|
||||||
|
return await db.diagram_filters.get({ diagramId });
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateDiagramFilter: StorageContext['updateDiagramFilter'] =
|
||||||
|
useCallback(
|
||||||
|
async (diagramId, filter): Promise<void> => {
|
||||||
|
await db.diagram_filters.put({
|
||||||
|
diagramId,
|
||||||
|
...filter,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteDiagramFilter: StorageContext['deleteDiagramFilter'] =
|
||||||
|
useCallback(
|
||||||
|
async (diagramId: string): Promise<void> => {
|
||||||
|
await db.diagram_filters.where({ diagramId }).delete();
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
const addTable: StorageContext['addTable'] = useCallback(
|
const addTable: StorageContext['addTable'] = useCallback(
|
||||||
async ({ diagramId, table }) => {
|
async ({ diagramId, table }) => {
|
||||||
await db.db_tables.add({
|
await db.db_tables.add({
|
||||||
@@ -756,6 +808,9 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
deleteCustomType,
|
deleteCustomType,
|
||||||
listCustomTypes,
|
listCustomTypes,
|
||||||
deleteDiagramCustomTypes,
|
deleteDiagramCustomTypes,
|
||||||
|
getDiagramFilter,
|
||||||
|
updateDiagramFilter,
|
||||||
|
deleteDiagramFilter,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@@ -20,12 +20,18 @@ import {
|
|||||||
} from '@/lib/data/export-metadata/export-sql-script';
|
} from '@/lib/data/export-metadata/export-sql-script';
|
||||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
|
|
||||||
import { Annoyed, Sparkles } from 'lucide-react';
|
import { Annoyed, Sparkles } from 'lucide-react';
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
import {
|
||||||
|
filterDependency,
|
||||||
|
filterRelationship,
|
||||||
|
filterTable,
|
||||||
|
} from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
export interface ExportSQLDialogProps extends BaseDialogProps {
|
export interface ExportSQLDialogProps extends BaseDialogProps {
|
||||||
targetDatabaseType: DatabaseType;
|
targetDatabaseType: DatabaseType;
|
||||||
@@ -36,7 +42,8 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
targetDatabaseType,
|
targetDatabaseType,
|
||||||
}) => {
|
}) => {
|
||||||
const { closeExportSQLDialog } = useDialog();
|
const { closeExportSQLDialog } = useDialog();
|
||||||
const { currentDiagram, filteredSchemas } = useChartDB();
|
const { currentDiagram } = useChartDB();
|
||||||
|
const { filter } = useDiagramFilter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [script, setScript] = React.useState<string>();
|
const [script, setScript] = React.useState<string>();
|
||||||
const [error, setError] = React.useState<boolean>(false);
|
const [error, setError] = React.useState<boolean>(false);
|
||||||
@@ -48,7 +55,16 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
const filteredDiagram: Diagram = {
|
const filteredDiagram: Diagram = {
|
||||||
...currentDiagram,
|
...currentDiagram,
|
||||||
tables: currentDiagram.tables?.filter((table) =>
|
tables: currentDiagram.tables?.filter((table) =>
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
),
|
),
|
||||||
relationships: currentDiagram.relationships?.filter((rel) => {
|
relationships: currentDiagram.relationships?.filter((rel) => {
|
||||||
const sourceTable = currentDiagram.tables?.find(
|
const sourceTable = currentDiagram.tables?.find(
|
||||||
@@ -60,11 +76,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
sourceTable &&
|
sourceTable &&
|
||||||
targetTable &&
|
targetTable &&
|
||||||
shouldShowTablesBySchemaFilter(
|
filterRelationship({
|
||||||
sourceTable,
|
tableA: {
|
||||||
filteredSchemas
|
id: sourceTable.id,
|
||||||
) &&
|
schema: sourceTable.schema,
|
||||||
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
|
},
|
||||||
|
tableB: {
|
||||||
|
id: targetTable.id,
|
||||||
|
schema: targetTable.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
dependencies: currentDiagram.dependencies?.filter((dep) => {
|
dependencies: currentDiagram.dependencies?.filter((dep) => {
|
||||||
@@ -77,11 +102,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
table &&
|
table &&
|
||||||
dependentTable &&
|
dependentTable &&
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
|
filterDependency({
|
||||||
shouldShowTablesBySchemaFilter(
|
tableA: {
|
||||||
dependentTable,
|
id: table.id,
|
||||||
filteredSchemas
|
schema: table.schema,
|
||||||
)
|
},
|
||||||
|
tableB: {
|
||||||
|
id: dependentTable.id,
|
||||||
|
schema: dependentTable.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -101,7 +135,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
signal: abortControllerRef.current?.signal,
|
signal: abortControllerRef.current?.signal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [targetDatabaseType, currentDiagram, filteredSchemas]);
|
}, [targetDatabaseType, currentDiagram, filter]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialog.open) {
|
if (!dialog.open) {
|
||||||
|
@@ -28,6 +28,7 @@ import {
|
|||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
import { Label } from '@/components/label/label';
|
import { Label } from '@/components/label/label';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
|
||||||
export interface TableSchemaDialogProps extends BaseDialogProps {
|
export interface TableSchemaDialogProps extends BaseDialogProps {
|
||||||
table?: DBTable;
|
table?: DBTable;
|
||||||
@@ -44,7 +45,8 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
|||||||
allowSchemaCreation = false,
|
allowSchemaCreation = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { databaseType, filteredSchemas, filterSchemas } = useChartDB();
|
const { databaseType } = useChartDB();
|
||||||
|
const { addSchemaIfFiltered } = useDiagramFilter();
|
||||||
const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
|
const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
|
||||||
table?.schema
|
table?.schema
|
||||||
? schemaNameToSchemaId(table.schema)
|
? schemaNameToSchemaId(table.schema)
|
||||||
@@ -112,18 +114,14 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
|||||||
onConfirm({ schema });
|
onConfirm({ schema });
|
||||||
}
|
}
|
||||||
|
|
||||||
filterSchemas([
|
addSchemaIfFiltered(createdSchemaId);
|
||||||
...(filteredSchemas ?? schemas.map((s) => s.id)),
|
|
||||||
createdSchemaId,
|
|
||||||
]);
|
|
||||||
}, [
|
}, [
|
||||||
onConfirm,
|
onConfirm,
|
||||||
selectedSchemaId,
|
selectedSchemaId,
|
||||||
schemas,
|
schemas,
|
||||||
isCreatingNew,
|
isCreatingNew,
|
||||||
newSchemaName,
|
newSchemaName,
|
||||||
filteredSchemas,
|
addSchemaIfFiltered,
|
||||||
filterSchemas,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const schemaOptions: SelectBoxOption[] = useMemo(
|
const schemaOptions: SelectBoxOption[] = useMemo(
|
||||||
|
@@ -72,15 +72,6 @@ export const ar: LanguageTranslation = {
|
|||||||
cancel: 'إلغاء',
|
cancel: 'إلغاء',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'مخططات متعددة',
|
|
||||||
description:
|
|
||||||
'{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'لا شيء',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'فشل النسخ',
|
title: 'فشل النسخ',
|
||||||
@@ -115,10 +106,6 @@ export const ar: LanguageTranslation = {
|
|||||||
copied: '!تم النسخ',
|
copied: '!تم النسخ',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: ':المخطط',
|
|
||||||
filter_by_schema: 'تصفية حسب المخطط',
|
|
||||||
search_schema: '...بحث في المخطط',
|
|
||||||
no_schemas_found: '.لم يتم العثور على مخططات',
|
|
||||||
view_all_options: '...عرض جميع الخيارات',
|
view_all_options: '...عرض جميع الخيارات',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'الجداول',
|
tables: 'الجداول',
|
||||||
|
@@ -73,15 +73,6 @@ export const bn: LanguageTranslation = {
|
|||||||
cancel: 'বাতিল করুন',
|
cancel: 'বাতিল করুন',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'বহু স্কিমা',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'কিছুই না',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'কপি ব্যর্থ হয়েছে',
|
title: 'কপি ব্যর্থ হয়েছে',
|
||||||
@@ -116,10 +107,6 @@ export const bn: LanguageTranslation = {
|
|||||||
copied: 'অনুলিপি সম্পন্ন!',
|
copied: 'অনুলিপি সম্পন্ন!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'স্কিমা:',
|
|
||||||
filter_by_schema: 'স্কিমা দ্বারা ফিল্টার করুন',
|
|
||||||
search_schema: 'স্কিমা খুঁজুন...',
|
|
||||||
no_schemas_found: 'কোনো স্কিমা পাওয়া যায়নি।',
|
|
||||||
view_all_options: 'সমস্ত বিকল্প দেখুন...',
|
view_all_options: 'সমস্ত বিকল্প দেখুন...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'টেবিল',
|
tables: 'টেবিল',
|
||||||
|
@@ -73,15 +73,6 @@ export const de: LanguageTranslation = {
|
|||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Mehrere Schemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} Schemas in diesem Diagramm. Derzeit angezeigt: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'Keine',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Kopieren fehlgeschlagen',
|
title: 'Kopieren fehlgeschlagen',
|
||||||
@@ -117,10 +108,6 @@ export const de: LanguageTranslation = {
|
|||||||
copied: 'Kopiert!',
|
copied: 'Kopiert!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schema:',
|
|
||||||
filter_by_schema: 'Nach Schema filtern',
|
|
||||||
search_schema: 'Schema suchen...',
|
|
||||||
no_schemas_found: 'Keine Schemas gefunden.',
|
|
||||||
view_all_options: 'Alle Optionen anzeigen...',
|
view_all_options: 'Alle Optionen anzeigen...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tabellen',
|
tables: 'Tabellen',
|
||||||
|
@@ -71,14 +71,6 @@ export const en = {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Multiple Schemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} schemas in this diagram. Currently displaying: {{formattedSchemas}}.',
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Copy failed',
|
title: 'Copy failed',
|
||||||
@@ -113,10 +105,6 @@ export const en = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schema:',
|
|
||||||
filter_by_schema: 'Filter by schema',
|
|
||||||
search_schema: 'Search schema...',
|
|
||||||
no_schemas_found: 'No schemas found.',
|
|
||||||
view_all_options: 'View all Options...',
|
view_all_options: 'View all Options...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tables',
|
tables: 'Tables',
|
||||||
|
@@ -106,10 +106,6 @@ export const es: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Esquema:',
|
|
||||||
filter_by_schema: 'Filtrar por esquema',
|
|
||||||
search_schema: 'Buscar esquema...',
|
|
||||||
no_schemas_found: 'No se encontraron esquemas.',
|
|
||||||
view_all_options: 'Ver todas las opciones...',
|
view_all_options: 'Ver todas las opciones...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tablas',
|
tables: 'Tablas',
|
||||||
@@ -424,14 +420,6 @@ export const es: LanguageTranslation = {
|
|||||||
confirm: '¡Claro!',
|
confirm: '¡Claro!',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Múltiples Esquemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} esquemas en este diagrama. Actualmente mostrando: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'nada',
|
|
||||||
},
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
export_diagram_dialog: {
|
export_diagram_dialog: {
|
||||||
title: 'Export Diagram',
|
title: 'Export Diagram',
|
||||||
|
@@ -105,10 +105,6 @@ export const fr: LanguageTranslation = {
|
|||||||
copied: 'Copié !',
|
copied: 'Copié !',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schéma:',
|
|
||||||
filter_by_schema: 'Filtrer par schéma',
|
|
||||||
search_schema: 'Rechercher un schéma...',
|
|
||||||
no_schemas_found: 'Aucun schéma trouvé.',
|
|
||||||
view_all_options: 'Voir toutes les Options...',
|
view_all_options: 'Voir toutes les Options...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tables',
|
tables: 'Tables',
|
||||||
@@ -357,15 +353,6 @@ export const fr: LanguageTranslation = {
|
|||||||
transparent_description: 'Remove background color from image.',
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Schémas Multiples',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} schémas dans ce diagramme. Actuellement affiché(s) : {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'Aucun',
|
|
||||||
},
|
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
title: 'Sélectionner un Schéma',
|
title: 'Sélectionner un Schéma',
|
||||||
description:
|
description:
|
||||||
|
@@ -73,15 +73,6 @@ export const gu: LanguageTranslation = {
|
|||||||
cancel: 'રદ કરો',
|
cancel: 'રદ કરો',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'કઈંક વધારે સ્કીમા',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} સ્કીમા આ ડાયાગ્રામમાં છે. હાલમાં દર્શાવેલ છે: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'કઈ નહીં',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'નકલ નિષ્ફળ',
|
title: 'નકલ નિષ્ફળ',
|
||||||
@@ -116,10 +107,6 @@ export const gu: LanguageTranslation = {
|
|||||||
copied: 'નકલ થયું!',
|
copied: 'નકલ થયું!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'સ્કીમા:',
|
|
||||||
filter_by_schema: 'સ્કીમા દ્વારા ફિલ્ટર કરો',
|
|
||||||
search_schema: 'સ્કીમા શોધો...',
|
|
||||||
no_schemas_found: 'કોઈ સ્કીમા મળ્યા નથી.',
|
|
||||||
view_all_options: 'બધા વિકલ્પો જુઓ...',
|
view_all_options: 'બધા વિકલ્પો જુઓ...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'ટેબલ્સ',
|
tables: 'ટેબલ્સ',
|
||||||
|
@@ -72,15 +72,6 @@ export const hi: LanguageTranslation = {
|
|||||||
cancel: 'रद्द करें',
|
cancel: 'रद्द करें',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'एकाधिक स्कीमा',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} स्कीमा इस आरेख में हैं। वर्तमान में प्रदर्शित: {{formattedSchemas}}।',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'कोई नहीं',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'कॉपी असफल',
|
title: 'कॉपी असफल',
|
||||||
@@ -116,10 +107,6 @@ export const hi: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'स्कीमा:',
|
|
||||||
filter_by_schema: 'स्कीमा द्वारा फ़िल्टर करें',
|
|
||||||
search_schema: 'स्कीमा खोजें...',
|
|
||||||
no_schemas_found: 'कोई स्कीमा नहीं मिला।',
|
|
||||||
view_all_options: 'सभी विकल्प देखें...',
|
view_all_options: 'सभी विकल्प देखें...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'तालिकाएँ',
|
tables: 'तालिकाएँ',
|
||||||
|
@@ -71,14 +71,6 @@ export const hr: LanguageTranslation = {
|
|||||||
cancel: 'Odustani',
|
cancel: 'Odustani',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Više shema',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} shema u ovom dijagramu. Trenutno prikazano: {{formattedSchemas}}.',
|
|
||||||
show_me: 'Prikaži mi',
|
|
||||||
none: 'nijedna',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Kopiranje neuspješno',
|
title: 'Kopiranje neuspješno',
|
||||||
@@ -113,10 +105,6 @@ export const hr: LanguageTranslation = {
|
|||||||
copied: 'Kopirano!',
|
copied: 'Kopirano!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Shema:',
|
|
||||||
filter_by_schema: 'Filtriraj po shemi',
|
|
||||||
search_schema: 'Pretraži shemu...',
|
|
||||||
no_schemas_found: 'Nema pronađenih shema.',
|
|
||||||
view_all_options: 'Prikaži sve opcije...',
|
view_all_options: 'Prikaži sve opcije...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tablice',
|
tables: 'Tablice',
|
||||||
|
@@ -72,15 +72,6 @@ export const id_ID: LanguageTranslation = {
|
|||||||
cancel: 'Batal',
|
cancel: 'Batal',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Schema Lebih dari satu',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} schema di diagram ini. Sedang ditampilkan: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'Tidak ada',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Gagal menyalin',
|
title: 'Gagal menyalin',
|
||||||
@@ -115,10 +106,6 @@ export const id_ID: LanguageTranslation = {
|
|||||||
copied: 'Tersalin!',
|
copied: 'Tersalin!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Skema:',
|
|
||||||
filter_by_schema: 'Saring berdasarkan skema',
|
|
||||||
search_schema: 'Cari skema...',
|
|
||||||
no_schemas_found: 'Tidak ada skema yang ditemukan.',
|
|
||||||
view_all_options: 'Tampilkan Semua Pilihan...',
|
view_all_options: 'Tampilkan Semua Pilihan...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tabel',
|
tables: 'Tabel',
|
||||||
|
@@ -74,15 +74,6 @@ export const ja: LanguageTranslation = {
|
|||||||
cancel: 'キャンセル',
|
cancel: 'キャンセル',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '複数のスキーマ',
|
|
||||||
description:
|
|
||||||
'このダイアグラムには{{schemasCount}}個のスキーマがあります。現在表示中: {{formattedSchemas}}。',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'なし',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'コピー失敗',
|
title: 'コピー失敗',
|
||||||
@@ -119,10 +110,6 @@ export const ja: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'スキーマ:',
|
|
||||||
filter_by_schema: 'スキーマでフィルタ',
|
|
||||||
search_schema: 'スキーマを検索...',
|
|
||||||
no_schemas_found: 'スキーマが見つかりません。',
|
|
||||||
view_all_options: 'すべてのオプションを表示...',
|
view_all_options: 'すべてのオプションを表示...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'テーブル',
|
tables: 'テーブル',
|
||||||
|
@@ -72,15 +72,6 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
cancel: '취소',
|
cancel: '취소',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '다중 스키마',
|
|
||||||
description:
|
|
||||||
'현재 다이어그램에 {{schemasCount}}개의 스키마가 있습니다. Currently displaying: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: '없음',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: '복사 실패',
|
title: '복사 실패',
|
||||||
@@ -115,10 +106,6 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
copied: '복사됨!',
|
copied: '복사됨!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: '스키마:',
|
|
||||||
filter_by_schema: '스키마로 필터링',
|
|
||||||
search_schema: '스키마 검색...',
|
|
||||||
no_schemas_found: '스키마를 찾을 수 없습니다.',
|
|
||||||
view_all_options: '전체 옵션 보기...',
|
view_all_options: '전체 옵션 보기...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: '테이블',
|
tables: '테이블',
|
||||||
|
@@ -73,15 +73,6 @@ export const mr: LanguageTranslation = {
|
|||||||
cancel: 'रद्द करा',
|
cancel: 'रद्द करा',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'एकाधिक स्कीमा',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} स्कीमा या आरेखात आहेत. सध्या दाखवत आहोत: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'काहीही नाही',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'कॉपी अयशस्वी',
|
title: 'कॉपी अयशस्वी',
|
||||||
@@ -118,10 +109,6 @@ export const mr: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'स्कीमा:',
|
|
||||||
filter_by_schema: 'स्कीमा द्वारे फिल्टर करा',
|
|
||||||
search_schema: 'स्कीमा शोधा...',
|
|
||||||
no_schemas_found: 'कोणतेही स्कीमा सापडले नाहीत.',
|
|
||||||
view_all_options: 'सर्व पर्याय पहा...',
|
view_all_options: 'सर्व पर्याय पहा...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'टेबल्स',
|
tables: 'टेबल्स',
|
||||||
|
@@ -73,15 +73,6 @@ export const ne: LanguageTranslation = {
|
|||||||
cancel: 'रद्द गर्नुहोस्',
|
cancel: 'रद्द गर्नुहोस्',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'विविध स्कीमहरू',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} डायाग्राममा स्कीमहरू। हालको रूपमा देखाइएको छ: {{formattedSchemas}}।',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'कुनै पनि छैन',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'प्रतिलिपि असफल',
|
title: 'प्रतिलिपि असफल',
|
||||||
@@ -116,10 +107,6 @@ export const ne: LanguageTranslation = {
|
|||||||
copied: 'प्रतिलिपि गरियो!',
|
copied: 'प्रतिलिपि गरियो!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'स्कीम:',
|
|
||||||
filter_by_schema: 'स्कीम अनुसार फिल्टर गर्नुहोस्',
|
|
||||||
search_schema: 'स्कीम खोज्नुहोस्...',
|
|
||||||
no_schemas_found: 'कुनै स्कीमहरू फेला परेनन्',
|
|
||||||
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
|
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'तालिकाहरू',
|
tables: 'तालिकाहरू',
|
||||||
|
@@ -73,15 +73,6 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Múltiplos Esquemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} esquemas neste diagrama. Atualmente exibindo: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'nenhum',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Falha na cópia',
|
title: 'Falha na cópia',
|
||||||
@@ -116,10 +107,6 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
copied: 'Copiado!',
|
copied: 'Copiado!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Esquema:',
|
|
||||||
filter_by_schema: 'Filtrar por esquema',
|
|
||||||
search_schema: 'Buscar esquema...',
|
|
||||||
no_schemas_found: 'Nenhum esquema encontrado.',
|
|
||||||
view_all_options: 'Ver todas as Opções...',
|
view_all_options: 'Ver todas as Opções...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tabelas',
|
tables: 'Tabelas',
|
||||||
|
@@ -71,15 +71,6 @@ export const ru: LanguageTranslation = {
|
|||||||
cancel: 'Отменить',
|
cancel: 'Отменить',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Множественные схемы',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} схем в этой диаграмме. В данный момент отображается: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'никто',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Ошибка копирования',
|
title: 'Ошибка копирования',
|
||||||
@@ -113,10 +104,6 @@ export const ru: LanguageTranslation = {
|
|||||||
show_less: 'Показать меньше',
|
show_less: 'Показать меньше',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Схема:',
|
|
||||||
filter_by_schema: 'Фильтр по схеме',
|
|
||||||
search_schema: 'Схема поиска...',
|
|
||||||
no_schemas_found: 'Схемы не найдены.',
|
|
||||||
view_all_options: 'Просмотреть все варианты...',
|
view_all_options: 'Просмотреть все варианты...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Таблицы',
|
tables: 'Таблицы',
|
||||||
|
@@ -73,15 +73,6 @@ export const te: LanguageTranslation = {
|
|||||||
cancel: 'రద్దు',
|
cancel: 'రద్దు',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'బహుళ స్కీమాలు',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} స్కీమాలు ఈ చిత్రంలో ఉన్నాయి. ప్రస్తుత స్కీమాలు: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'ఎదరికాదు',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'కాపీ విఫలమైంది',
|
title: 'కాపీ విఫలమైంది',
|
||||||
@@ -116,10 +107,6 @@ export const te: LanguageTranslation = {
|
|||||||
copied: 'కాపీ చేయబడింది!',
|
copied: 'కాపీ చేయబడింది!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'స్కీమా:',
|
|
||||||
filter_by_schema: 'స్కీమా ద్వారా ఫిల్టర్ చేయండి',
|
|
||||||
search_schema: 'స్కీమా కోసం శోధించండి...',
|
|
||||||
no_schemas_found: 'ఏ స్కీమాలు కూడా కనుగొనబడలేదు.',
|
|
||||||
view_all_options: 'అన్ని ఎంపికలను చూడండి...',
|
view_all_options: 'అన్ని ఎంపికలను చూడండి...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'పట్టికలు',
|
tables: 'పట్టికలు',
|
||||||
|
@@ -73,15 +73,6 @@ export const tr: LanguageTranslation = {
|
|||||||
cancel: 'İptal',
|
cancel: 'İptal',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Birden Fazla Şema',
|
|
||||||
description:
|
|
||||||
'Bu diyagramda {{schemasCount}} şema var. Şu anda görüntülenen: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'yok',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Kopyalama başarısız',
|
title: 'Kopyalama başarısız',
|
||||||
@@ -115,10 +106,6 @@ export const tr: LanguageTranslation = {
|
|||||||
copy_to_clipboard: 'Panoya Kopyala',
|
copy_to_clipboard: 'Panoya Kopyala',
|
||||||
copied: 'Kopyalandı!',
|
copied: 'Kopyalandı!',
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Şema:',
|
|
||||||
filter_by_schema: 'Şemaya Göre Filtrele',
|
|
||||||
search_schema: 'Şema ara...',
|
|
||||||
no_schemas_found: 'Şema bulunamadı.',
|
|
||||||
view_all_options: 'Tüm Seçenekleri Gör...',
|
view_all_options: 'Tüm Seçenekleri Gör...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tablolar',
|
tables: 'Tablolar',
|
||||||
|
@@ -71,15 +71,6 @@ export const uk: LanguageTranslation = {
|
|||||||
cancel: 'Скасувати',
|
cancel: 'Скасувати',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Кілька схем',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} схеми на цій діаграмі. Зараз відображається: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'немає',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Помилка копіювання',
|
title: 'Помилка копіювання',
|
||||||
@@ -114,10 +105,6 @@ export const uk: LanguageTranslation = {
|
|||||||
copied: 'Скопійовано!',
|
copied: 'Скопійовано!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Схема:',
|
|
||||||
filter_by_schema: 'Фільтрувати за схемою',
|
|
||||||
search_schema: 'Пошук схеми…',
|
|
||||||
no_schemas_found: 'Схеми не знайдено.',
|
|
||||||
view_all_options: 'Переглянути всі параметри…',
|
view_all_options: 'Переглянути всі параметри…',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Таблиці',
|
tables: 'Таблиці',
|
||||||
|
@@ -72,15 +72,6 @@ export const vi: LanguageTranslation = {
|
|||||||
cancel: 'Hủy',
|
cancel: 'Hủy',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Có nhiều lược đồ',
|
|
||||||
description:
|
|
||||||
'Có {{schemasCount}} lược đồ trong sơ đồ này. Hiện đang hiển thị: {{formattedSchemas}}.',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: 'không có',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Sao chép thất bại',
|
title: 'Sao chép thất bại',
|
||||||
@@ -115,10 +106,6 @@ export const vi: LanguageTranslation = {
|
|||||||
copied: 'Đã sao chép!',
|
copied: 'Đã sao chép!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Lược đồ:',
|
|
||||||
filter_by_schema: 'Lọc bởi lược đồ',
|
|
||||||
search_schema: 'Tìm kiếm lược đồ...',
|
|
||||||
no_schemas_found: 'Không tìm thấy lược đồ.',
|
|
||||||
view_all_options: 'Xem tất cả tùy chọn...',
|
view_all_options: 'Xem tất cả tùy chọn...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Bảng',
|
tables: 'Bảng',
|
||||||
|
@@ -69,15 +69,6 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '多个模式',
|
|
||||||
description:
|
|
||||||
'此关系图中有 {{schemasCount}} 个模式,当前显示:{{formattedSchemas}}。',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: '无',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: '复制失败',
|
title: '复制失败',
|
||||||
@@ -112,10 +103,6 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
copied: '复制了!',
|
copied: '复制了!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: '模式:',
|
|
||||||
filter_by_schema: '按模式筛选',
|
|
||||||
search_schema: '搜索模式...',
|
|
||||||
no_schemas_found: '未找到模式。',
|
|
||||||
view_all_options: '查看所有选项...',
|
view_all_options: '查看所有选项...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: '表',
|
tables: '表',
|
||||||
|
@@ -69,15 +69,6 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '多重 Schema',
|
|
||||||
description:
|
|
||||||
'此圖表中包含 {{schemasCount}} 個 Schema,目前顯示:{{formattedSchemas}}。',
|
|
||||||
// TODO: Translate
|
|
||||||
show_me: 'Show me',
|
|
||||||
none: '無',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: '複製失敗',
|
title: '複製失敗',
|
||||||
@@ -112,10 +103,6 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
copied: '已複製!',
|
copied: '已複製!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schema:',
|
|
||||||
filter_by_schema: '依 Schema 篩選',
|
|
||||||
search_schema: '搜尋 Schema...',
|
|
||||||
no_schemas_found: '未找到 Schema。',
|
|
||||||
view_all_options: '顯示所有選項...',
|
view_all_options: '顯示所有選項...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: '表格',
|
tables: '表格',
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
export interface ChartDBConfig {
|
export interface ChartDBConfig {
|
||||||
defaultDiagramId: string;
|
defaultDiagramId: string;
|
||||||
exportActions?: Date[];
|
exportActions?: Date[];
|
||||||
hiddenTablesByDiagram?: Record<string, string[]>; // Maps diagram ID to array of hidden table IDs
|
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { ViewInfo } from '../data/import-metadata/metadata-types/view-info';
|
import type { ViewInfo } from '../data/import-metadata/metadata-types/view-info';
|
||||||
import { DatabaseType } from './database-type';
|
import { DatabaseType } from './database-type';
|
||||||
import {
|
import { schemaNameToDomainSchemaName } from './db-schema';
|
||||||
schemaNameToDomainSchemaName,
|
|
||||||
schemaNameToSchemaId,
|
|
||||||
} from './db-schema';
|
|
||||||
import { decodeViewDefinition, type DBTable } from './db-table';
|
import { decodeViewDefinition, type DBTable } from './db-table';
|
||||||
import { generateId } from '@/lib/utils';
|
import { generateId } from '@/lib/utils';
|
||||||
import type { AST } from 'node-sql-parser';
|
import type { AST } from 'node-sql-parser';
|
||||||
@@ -27,18 +24,6 @@ export const dbDependencySchema: z.ZodType<DBDependency> = z.object({
|
|||||||
createdAt: z.number(),
|
createdAt: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const shouldShowDependencyBySchemaFilter = (
|
|
||||||
dependency: DBDependency,
|
|
||||||
filteredSchemas?: string[]
|
|
||||||
): boolean =>
|
|
||||||
!filteredSchemas ||
|
|
||||||
!dependency.schema ||
|
|
||||||
!dependency.dependentSchema ||
|
|
||||||
(filteredSchemas.includes(schemaNameToSchemaId(dependency.schema)) &&
|
|
||||||
filteredSchemas.includes(
|
|
||||||
schemaNameToSchemaId(dependency.dependentSchema)
|
|
||||||
));
|
|
||||||
|
|
||||||
const astDatabaseTypes: Record<DatabaseType, string> = {
|
const astDatabaseTypes: Record<DatabaseType, string> = {
|
||||||
[DatabaseType.POSTGRESQL]: 'postgresql',
|
[DatabaseType.POSTGRESQL]: 'postgresql',
|
||||||
[DatabaseType.MYSQL]: 'postgresql',
|
[DatabaseType.MYSQL]: 'postgresql',
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { ForeignKeyInfo } from '../data/import-metadata/metadata-types/foreign-key-info';
|
import type { ForeignKeyInfo } from '../data/import-metadata/metadata-types/foreign-key-info';
|
||||||
import type { DBField } from './db-field';
|
import type { DBField } from './db-field';
|
||||||
import {
|
import { schemaNameToDomainSchemaName } from './db-schema';
|
||||||
schemaNameToDomainSchemaName,
|
|
||||||
schemaNameToSchemaId,
|
|
||||||
} from './db-schema';
|
|
||||||
import type { DBTable } from './db-table';
|
import type { DBTable } from './db-table';
|
||||||
import { generateId } from '@/lib/utils';
|
import { generateId } from '@/lib/utils';
|
||||||
|
|
||||||
@@ -43,20 +40,6 @@ export type RelationshipType =
|
|||||||
| 'many_to_many';
|
| 'many_to_many';
|
||||||
export type Cardinality = 'one' | 'many';
|
export type Cardinality = 'one' | 'many';
|
||||||
|
|
||||||
export const shouldShowRelationshipBySchemaFilter = (
|
|
||||||
relationship: DBRelationship,
|
|
||||||
filteredSchemas?: string[]
|
|
||||||
): boolean =>
|
|
||||||
!filteredSchemas ||
|
|
||||||
!relationship.sourceSchema ||
|
|
||||||
!relationship.targetSchema ||
|
|
||||||
(filteredSchemas.includes(
|
|
||||||
schemaNameToSchemaId(relationship.sourceSchema)
|
|
||||||
) &&
|
|
||||||
filteredSchemas.includes(
|
|
||||||
schemaNameToSchemaId(relationship.targetSchema)
|
|
||||||
));
|
|
||||||
|
|
||||||
const determineCardinality = (
|
const determineCardinality = (
|
||||||
field: DBField,
|
field: DBField,
|
||||||
isTablePKComplex: boolean
|
isTablePKComplex: boolean
|
||||||
|
@@ -18,10 +18,7 @@ import {
|
|||||||
deepCopy,
|
deepCopy,
|
||||||
generateId,
|
generateId,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import {
|
import { schemaNameToDomainSchemaName } from './db-schema';
|
||||||
schemaNameToDomainSchemaName,
|
|
||||||
schemaNameToSchemaId,
|
|
||||||
} from './db-schema';
|
|
||||||
import { DatabaseType } from './database-type';
|
import { DatabaseType } from './database-type';
|
||||||
import type { DatabaseMetadata } from '../data/import-metadata/metadata-types/database-metadata';
|
import type { DatabaseMetadata } from '../data/import-metadata/metadata-types/database-metadata';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -77,26 +74,6 @@ export const generateTableKey = ({
|
|||||||
tableName: string;
|
tableName: string;
|
||||||
}) => `${schemaNameToDomainSchemaName(schemaName) ?? ''}.${tableName}`;
|
}) => `${schemaNameToDomainSchemaName(schemaName) ?? ''}.${tableName}`;
|
||||||
|
|
||||||
export const shouldShowTableSchemaBySchemaFilter = ({
|
|
||||||
filteredSchemas,
|
|
||||||
tableSchema,
|
|
||||||
}: {
|
|
||||||
tableSchema?: string | null;
|
|
||||||
filteredSchemas?: string[];
|
|
||||||
}): boolean =>
|
|
||||||
!filteredSchemas ||
|
|
||||||
!tableSchema ||
|
|
||||||
filteredSchemas.includes(schemaNameToSchemaId(tableSchema));
|
|
||||||
|
|
||||||
export const shouldShowTablesBySchemaFilter = (
|
|
||||||
table: DBTable,
|
|
||||||
filteredSchemas?: string[]
|
|
||||||
): boolean =>
|
|
||||||
shouldShowTableSchemaBySchemaFilter({
|
|
||||||
filteredSchemas,
|
|
||||||
tableSchema: table?.schema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const decodeViewDefinition = (
|
export const decodeViewDefinition = (
|
||||||
databaseType: DatabaseType,
|
databaseType: DatabaseType,
|
||||||
viewDefinition?: string
|
viewDefinition?: string
|
||||||
|
147
src/lib/domain/diagram-filter/diagram-filter.ts
Normal file
147
src/lib/domain/diagram-filter/diagram-filter.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// union logic filter
|
||||||
|
export interface DiagramFilter {
|
||||||
|
schemaIds?: string[];
|
||||||
|
tableIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableInfo {
|
||||||
|
id: string;
|
||||||
|
schemaId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces/optimizes a DiagramFilter by removing redundant entries
|
||||||
|
* - Removes tableIds that belong to schemas already in schemaIds (union logic)
|
||||||
|
* - Consolidates complete schemas: if all tables from a schema are in tableIds, adds the schema to schemaIds
|
||||||
|
* - Returns undefined for both fields if everything is displayed
|
||||||
|
* - Returns empty arrays if nothing should be displayed
|
||||||
|
*/
|
||||||
|
export function reduceFilter(
|
||||||
|
filter: DiagramFilter,
|
||||||
|
tables: TableInfo[]
|
||||||
|
): DiagramFilter {
|
||||||
|
let { schemaIds, tableIds } = filter;
|
||||||
|
|
||||||
|
// If no filters are defined, everything is visible
|
||||||
|
if (!schemaIds && !tableIds) {
|
||||||
|
return { schemaIds: undefined, tableIds: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all unique schema IDs from tables
|
||||||
|
const allSchemaIds = [
|
||||||
|
...new Set(tables.filter((t) => t.schemaId).map((t) => t.schemaId!)),
|
||||||
|
];
|
||||||
|
const allTableIds = tables.map((t) => t.id);
|
||||||
|
|
||||||
|
// in case its db with no schemas
|
||||||
|
if (allSchemaIds.length === 0) {
|
||||||
|
const tableSet = new Set(tableIds);
|
||||||
|
if (tableSet.size === allTableIds.length) {
|
||||||
|
return { schemaIds: undefined, tableIds: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { schemaIds: undefined, tableIds: Array.from(tableSet) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a map of schema to its tables
|
||||||
|
const schemaToTables = new Map<string, string[]>();
|
||||||
|
tables.forEach((table) => {
|
||||||
|
if (table.schemaId) {
|
||||||
|
if (!schemaToTables.has(table.schemaId)) {
|
||||||
|
schemaToTables.set(table.schemaId, []);
|
||||||
|
}
|
||||||
|
schemaToTables.get(table.schemaId)!.push(table.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Consolidate complete schemas: if all tables from a schema are in tableIds, add schema to schemaIds
|
||||||
|
if (tableIds) {
|
||||||
|
const tableSet = new Set(tableIds);
|
||||||
|
const consolidatedSchemaIds = new Set(schemaIds || []);
|
||||||
|
let consolidatedTableIds = [...tableIds];
|
||||||
|
|
||||||
|
for (const [schemaId, schemaTables] of schemaToTables.entries()) {
|
||||||
|
// Check if all tables from this schema are in tableIds
|
||||||
|
if (schemaTables.every((tableId) => tableSet.has(tableId))) {
|
||||||
|
// Add schema to schemaIds
|
||||||
|
consolidatedSchemaIds.add(schemaId);
|
||||||
|
// Remove these tables from tableIds
|
||||||
|
consolidatedTableIds = consolidatedTableIds.filter(
|
||||||
|
(id) => !schemaTables.includes(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaIds =
|
||||||
|
consolidatedSchemaIds.size > 0
|
||||||
|
? Array.from(consolidatedSchemaIds)
|
||||||
|
: schemaIds;
|
||||||
|
tableIds =
|
||||||
|
consolidatedTableIds.length > 0 ? consolidatedTableIds : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all schemas are in the filter, everything is visible
|
||||||
|
if (schemaIds && schemaIds.length === allSchemaIds.length) {
|
||||||
|
const schemasSet = new Set(schemaIds);
|
||||||
|
const allSchemasIncluded = allSchemaIds.every((id) =>
|
||||||
|
schemasSet.has(id)
|
||||||
|
);
|
||||||
|
if (allSchemasIncluded) {
|
||||||
|
return { schemaIds: undefined, tableIds: undefined };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If schemaIds is defined, remove tables from tableIds that belong to those schemas
|
||||||
|
let reducedTableIds = tableIds;
|
||||||
|
if (schemaIds && tableIds) {
|
||||||
|
const schemaSet = new Set(schemaIds);
|
||||||
|
reducedTableIds = tableIds.filter((tableId) => {
|
||||||
|
const table = tables.find((t) => t.id === tableId);
|
||||||
|
// Keep table in tableIds only if it doesn't belong to a schema in schemaIds
|
||||||
|
return !table?.schemaId || !schemaSet.has(table.schemaId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no tables remain after reduction, set to undefined
|
||||||
|
if (reducedTableIds.length === 0) {
|
||||||
|
reducedTableIds = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all tables are now visible (either through schemas or direct table IDs)
|
||||||
|
if (schemaIds && reducedTableIds) {
|
||||||
|
const schemaSet = new Set(schemaIds);
|
||||||
|
const tableSet = new Set(reducedTableIds);
|
||||||
|
|
||||||
|
const visibleTables = tables.filter((table) => {
|
||||||
|
// Table is visible if it's in tableIds OR its schema is in schemaIds
|
||||||
|
return (
|
||||||
|
tableSet.has(table.id) ||
|
||||||
|
(table.schemaId && schemaSet.has(table.schemaId))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (visibleTables.length === tables.length) {
|
||||||
|
return { schemaIds: undefined, tableIds: undefined };
|
||||||
|
}
|
||||||
|
} else if (schemaIds && !reducedTableIds) {
|
||||||
|
// Only schemaIds is defined, check if all tables are covered by schemas
|
||||||
|
const schemaSet = new Set(schemaIds);
|
||||||
|
const visibleTables = tables.filter(
|
||||||
|
(table) => table.schemaId && schemaSet.has(table.schemaId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (visibleTables.length === tables.length) {
|
||||||
|
return { schemaIds: undefined, tableIds: undefined };
|
||||||
|
}
|
||||||
|
} else if (!schemaIds && reducedTableIds) {
|
||||||
|
// Only tableIds is defined, check if all tables are in the filter
|
||||||
|
if (reducedTableIds.length === allTableIds.length) {
|
||||||
|
return { schemaIds: undefined, tableIds: undefined };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
schemaIds,
|
||||||
|
tableIds: reducedTableIds,
|
||||||
|
};
|
||||||
|
}
|
114
src/lib/domain/diagram-filter/filter.ts
Normal file
114
src/lib/domain/diagram-filter/filter.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { schemaNameToSchemaId } from '../db-schema';
|
||||||
|
import type { DiagramFilter } from './diagram-filter';
|
||||||
|
|
||||||
|
export const filterTable = ({
|
||||||
|
table,
|
||||||
|
filter,
|
||||||
|
options = { defaultSchema: undefined },
|
||||||
|
}: {
|
||||||
|
table: { id: string; schema?: string | null };
|
||||||
|
filter?: DiagramFilter;
|
||||||
|
options?: {
|
||||||
|
defaultSchema?: string;
|
||||||
|
};
|
||||||
|
}): boolean => {
|
||||||
|
if (!filter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter.tableIds && !filter.schemaIds) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.tableIds && filter.tableIds.includes(table.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableSchema = table.schema ?? options.defaultSchema;
|
||||||
|
|
||||||
|
if (
|
||||||
|
tableSchema &&
|
||||||
|
filter.schemaIds &&
|
||||||
|
filter.schemaIds.includes(schemaNameToSchemaId(tableSchema))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterTableBySchema = ({
|
||||||
|
table,
|
||||||
|
schemaIdsFilter,
|
||||||
|
options = { defaultSchema: undefined },
|
||||||
|
}: {
|
||||||
|
table: { id: string; schema?: string | null };
|
||||||
|
schemaIdsFilter?: string[];
|
||||||
|
options?: {
|
||||||
|
defaultSchema?: string;
|
||||||
|
};
|
||||||
|
}): boolean => {
|
||||||
|
if (!schemaIdsFilter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableSchemaId = table.schema ?? options.defaultSchema;
|
||||||
|
|
||||||
|
if (tableSchemaId) {
|
||||||
|
return schemaIdsFilter.includes(schemaNameToSchemaId(tableSchemaId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterSchema = ({
|
||||||
|
schemaId,
|
||||||
|
schemaIdsFilter,
|
||||||
|
}: {
|
||||||
|
schemaId?: string;
|
||||||
|
schemaIdsFilter?: string[];
|
||||||
|
}): boolean => {
|
||||||
|
if (!schemaIdsFilter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schemaId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return schemaIdsFilter.includes(schemaId);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterRelationship = ({
|
||||||
|
tableA: { id: tableAId, schema: tableASchema },
|
||||||
|
tableB: { id: tableBId, schema: tableBSchema },
|
||||||
|
filter,
|
||||||
|
options = { defaultSchema: undefined },
|
||||||
|
}: {
|
||||||
|
tableA: { id: string; schema?: string | null };
|
||||||
|
tableB: { id: string; schema?: string | null };
|
||||||
|
filter?: DiagramFilter;
|
||||||
|
options?: {
|
||||||
|
defaultSchema?: string;
|
||||||
|
};
|
||||||
|
}): boolean => {
|
||||||
|
if (!filter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTableAVisible = filterTable({
|
||||||
|
table: { id: tableAId, schema: tableASchema },
|
||||||
|
filter,
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isTableBVisible = filterTable({
|
||||||
|
table: { id: tableBId, schema: tableBSchema },
|
||||||
|
filter,
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
return isTableAVisible && isTableBVisible;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterDependency = filterRelationship;
|
@@ -11,12 +11,13 @@ import { useReactFlow } from '@xyflow/react';
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Table, Workflow, Group } from 'lucide-react';
|
import { Table, Workflow, Group } from 'lucide-react';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
|
||||||
export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { createTable, filteredSchemas, schemas, readonly, createArea } =
|
const { createTable, readonly, createArea } = useChartDB();
|
||||||
useChartDB();
|
const { schemasDisplayed } = useDiagramFilter();
|
||||||
const { openCreateRelationshipDialog, openTableSchemaDialog } = useDialog();
|
const { openCreateRelationshipDialog, openTableSchemaDialog } = useDialog();
|
||||||
const { screenToFlowPosition } = useReactFlow();
|
const { screenToFlowPosition } = useReactFlow();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -30,7 +31,7 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
|||||||
y: event.clientY,
|
y: event.clientY,
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((filteredSchemas?.length ?? 0) > 1) {
|
if (schemasDisplayed.length > 1) {
|
||||||
openTableSchemaDialog({
|
openTableSchemaDialog({
|
||||||
onConfirm: ({ schema }) =>
|
onConfirm: ({ schema }) =>
|
||||||
createTable({
|
createTable({
|
||||||
@@ -38,14 +39,12 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
|||||||
y: position.y,
|
y: position.y,
|
||||||
schema: schema.name,
|
schema: schema.name,
|
||||||
}),
|
}),
|
||||||
schemas: schemas.filter((schema) =>
|
schemas: schemasDisplayed,
|
||||||
filteredSchemas?.includes(schema.id)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const schema =
|
const schema =
|
||||||
filteredSchemas?.length === 1
|
schemasDisplayed?.length === 1
|
||||||
? schemas.find((s) => s.id === filteredSchemas[0])?.name
|
? schemasDisplayed[0]?.name
|
||||||
: undefined;
|
: undefined;
|
||||||
createTable({
|
createTable({
|
||||||
x: position.x,
|
x: position.x,
|
||||||
@@ -58,8 +57,7 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
|||||||
createTable,
|
createTable,
|
||||||
screenToFlowPosition,
|
screenToFlowPosition,
|
||||||
openTableSchemaDialog,
|
openTableSchemaDialog,
|
||||||
schemas,
|
schemasDisplayed,
|
||||||
filteredSchemas,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -10,13 +10,14 @@ import { useChartDB } from '@/hooks/use-chartdb';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from '@/components/button/button';
|
import { Button } from '@/components/button/button';
|
||||||
import { Input } from '@/components/input/input';
|
import { Input } from '@/components/input/input';
|
||||||
import { shouldShowTableSchemaBySchemaFilter } from '@/lib/domain/db-table';
|
|
||||||
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
||||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
import { useReactFlow } from '@xyflow/react';
|
import { useReactFlow } from '@xyflow/react';
|
||||||
import { TreeView } from '@/components/tree-view/tree-view';
|
import { TreeView } from '@/components/tree-view/tree-view';
|
||||||
import type { TreeNode } from '@/components/tree-view/tree';
|
import type { TreeNode } from '@/components/tree-view/tree';
|
||||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
||||||
|
import { filterSchema, filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
|
||||||
export interface CanvasFilterProps {
|
export interface CanvasFilterProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -24,10 +25,10 @@ export interface CanvasFilterProps {
|
|||||||
|
|
||||||
type NodeType = 'schema' | 'table';
|
type NodeType = 'schema' | 'table';
|
||||||
|
|
||||||
type SchemaContext = { name: string };
|
type SchemaContext = { name: string; visible: boolean };
|
||||||
type TableContext = {
|
type TableContext = {
|
||||||
tableSchema?: string | null;
|
tableSchema?: string | null;
|
||||||
hidden: boolean;
|
visible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NodeContext = {
|
type NodeContext = {
|
||||||
@@ -43,15 +44,14 @@ type RelevantTableData = {
|
|||||||
|
|
||||||
export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { tables, databaseType } = useChartDB();
|
||||||
const {
|
const {
|
||||||
tables,
|
filter,
|
||||||
databaseType,
|
toggleSchemaFilter,
|
||||||
hiddenTableIds,
|
toggleTableFilter,
|
||||||
addHiddenTableId,
|
clearTableIdsFilter,
|
||||||
removeHiddenTableId,
|
setTableIdsFilterEmpty,
|
||||||
filteredSchemas,
|
} = useDiagramFilter();
|
||||||
filterSchemas,
|
|
||||||
} = useChartDB();
|
|
||||||
const { fitView, setNodes } = useReactFlow();
|
const { fitView, setNodes } = useReactFlow();
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
||||||
@@ -69,14 +69,21 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
[tables]
|
[tables]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const databaseWithSchemas = useMemo(
|
||||||
|
() => !!defaultSchemas[databaseType],
|
||||||
|
[databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
// Convert tables to tree nodes
|
// Convert tables to tree nodes
|
||||||
const treeData = useMemo(() => {
|
const treeData = useMemo(() => {
|
||||||
// Group tables by schema
|
// Group tables by schema
|
||||||
const tablesBySchema = new Map<string, RelevantTableData[]>();
|
const tablesBySchema = new Map<string, RelevantTableData[]>();
|
||||||
|
|
||||||
relevantTableData.forEach((table) => {
|
relevantTableData.forEach((table) => {
|
||||||
const schema =
|
const schema = !databaseWithSchemas
|
||||||
table.schema ?? defaultSchemas[databaseType] ?? 'default';
|
? 'All Tables'
|
||||||
|
: (table.schema ?? defaultSchemas[databaseType] ?? 'default');
|
||||||
|
|
||||||
if (!tablesBySchema.has(schema)) {
|
if (!tablesBySchema.has(schema)) {
|
||||||
tablesBySchema.set(schema, []);
|
tablesBySchema.set(schema, []);
|
||||||
}
|
}
|
||||||
@@ -92,28 +99,52 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
const nodes: TreeNode<NodeType, NodeContext>[] = [];
|
const nodes: TreeNode<NodeType, NodeContext>[] = [];
|
||||||
|
|
||||||
tablesBySchema.forEach((schemaTables, schemaName) => {
|
tablesBySchema.forEach((schemaTables, schemaName) => {
|
||||||
const schemaId = schemaNameToSchemaId(schemaName);
|
let schemaVisible;
|
||||||
const schemaHidden = filteredSchemas
|
|
||||||
? !filteredSchemas.includes(schemaId)
|
if (databaseWithSchemas) {
|
||||||
: false;
|
const schemaId = schemaNameToSchemaId(schemaName);
|
||||||
|
schemaVisible = filterSchema({
|
||||||
|
schemaId,
|
||||||
|
schemaIdsFilter: filter?.schemaIds,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if at least one table is visible, the schema is considered visible
|
||||||
|
schemaVisible = schemaTables.some((table) =>
|
||||||
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const schemaNode: TreeNode<NodeType, NodeContext> = {
|
const schemaNode: TreeNode<NodeType, NodeContext> = {
|
||||||
id: `schema-${schemaName}`,
|
id: `schema-${schemaName}`,
|
||||||
name: `${schemaName} (${schemaTables.length})`,
|
name: `${schemaName} (${schemaTables.length})`,
|
||||||
type: 'schema',
|
type: 'schema',
|
||||||
isFolder: true,
|
isFolder: true,
|
||||||
icon: Database,
|
icon: Database,
|
||||||
context: { name: schemaName },
|
context: { name: schemaName, visible: schemaVisible },
|
||||||
className: schemaHidden ? 'opacity-50' : '',
|
className: !schemaVisible ? 'opacity-50' : '',
|
||||||
children: schemaTables.map(
|
children: schemaTables.map(
|
||||||
(table): TreeNode<NodeType, NodeContext> => {
|
(table): TreeNode<NodeType, NodeContext> => {
|
||||||
const tableHidden =
|
const tableVisible = filterTable({
|
||||||
hiddenTableIds?.includes(table.id) ?? false;
|
table: {
|
||||||
const visibleBySchema =
|
id: table.id,
|
||||||
shouldShowTableSchemaBySchemaFilter({
|
schema: table.schema,
|
||||||
tableSchema: table.schema,
|
},
|
||||||
filteredSchemas,
|
filter,
|
||||||
});
|
options: {
|
||||||
const hidden = tableHidden || !visibleBySchema;
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const hidden = !tableVisible;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: table.id,
|
id: table.id,
|
||||||
@@ -123,7 +154,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
icon: Table,
|
icon: Table,
|
||||||
context: {
|
context: {
|
||||||
tableSchema: table.schema,
|
tableSchema: table.schema,
|
||||||
hidden: tableHidden,
|
visible: tableVisible,
|
||||||
},
|
},
|
||||||
className: hidden ? 'opacity-50' : '',
|
className: hidden ? 'opacity-50' : '',
|
||||||
};
|
};
|
||||||
@@ -134,7 +165,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
}, [relevantTableData, databaseType, hiddenTableIds, filteredSchemas]);
|
}, [relevantTableData, databaseType, filter, databaseWithSchemas]);
|
||||||
|
|
||||||
// Initialize expanded state with all schemas expanded
|
// Initialize expanded state with all schemas expanded
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
@@ -170,17 +201,6 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
return result;
|
return result;
|
||||||
}, [treeData, searchQuery]);
|
}, [treeData, searchQuery]);
|
||||||
|
|
||||||
const toggleTableVisibility = useCallback(
|
|
||||||
async (tableId: string, hidden: boolean) => {
|
|
||||||
if (hidden) {
|
|
||||||
await addHiddenTableId(tableId);
|
|
||||||
} else {
|
|
||||||
await removeHiddenTableId(tableId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[addHiddenTableId, removeHiddenTableId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const focusOnTable = useCallback(
|
const focusOnTable = useCallback(
|
||||||
(tableId: string) => {
|
(tableId: string) => {
|
||||||
// Make sure the table is visible
|
// Make sure the table is visible
|
||||||
@@ -222,9 +242,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
if (node.type === 'schema') {
|
if (node.type === 'schema') {
|
||||||
const schemaContext = node.context as SchemaContext;
|
const schemaContext = node.context as SchemaContext;
|
||||||
const schemaId = schemaNameToSchemaId(schemaContext.name);
|
const schemaId = schemaNameToSchemaId(schemaContext.name);
|
||||||
const schemaHidden = filteredSchemas
|
const schemaVisible = node.context.visible;
|
||||||
? !filteredSchemas.includes(schemaId)
|
|
||||||
: false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -233,30 +251,20 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
className="size-7 h-fit p-0"
|
className="size-7 h-fit p-0"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// unhide all tables in this schema
|
|
||||||
node.children?.forEach((child) => {
|
if (databaseWithSchemas) {
|
||||||
if (
|
toggleSchemaFilter(schemaId);
|
||||||
child.type === 'table' &&
|
|
||||||
hiddenTableIds?.includes(child.id)
|
|
||||||
) {
|
|
||||||
removeHiddenTableId(child.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (schemaHidden) {
|
|
||||||
filterSchemas([
|
|
||||||
...(filteredSchemas ?? []),
|
|
||||||
schemaId,
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
filterSchemas(
|
// Toggle visibility of all tables in this schema
|
||||||
filteredSchemas?.filter(
|
if (node.context.visible) {
|
||||||
(s) => s !== schemaId
|
setTableIdsFilterEmpty();
|
||||||
) ?? []
|
} else {
|
||||||
);
|
clearTableIdsFilter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{schemaHidden ? (
|
{!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" />
|
||||||
@@ -268,13 +276,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
if (node.type === 'table') {
|
if (node.type === 'table') {
|
||||||
const tableId = node.id;
|
const tableId = node.id;
|
||||||
const tableContext = node.context as TableContext;
|
const tableContext = node.context as TableContext;
|
||||||
const hidden = tableContext.hidden;
|
const tableVisible = tableContext.visible;
|
||||||
const tableSchema = tableContext.tableSchema;
|
|
||||||
|
|
||||||
const visibleBySchema = shouldShowTableSchemaBySchemaFilter({
|
|
||||||
tableSchema,
|
|
||||||
filteredSchemas,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -283,35 +285,10 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
className="size-7 h-fit p-0"
|
className="size-7 h-fit p-0"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!visibleBySchema && tableSchema) {
|
toggleTableFilter(tableId);
|
||||||
// Unhide schema and hide all other tables
|
|
||||||
const schemaId =
|
|
||||||
schemaNameToSchemaId(tableSchema);
|
|
||||||
filterSchemas([
|
|
||||||
...(filteredSchemas ?? []),
|
|
||||||
schemaId,
|
|
||||||
]);
|
|
||||||
const schemaNode = treeData.find(
|
|
||||||
(s) =>
|
|
||||||
(s.context as SchemaContext).name ===
|
|
||||||
tableSchema
|
|
||||||
);
|
|
||||||
if (schemaNode) {
|
|
||||||
schemaNode.children?.forEach((child) => {
|
|
||||||
if (
|
|
||||||
child.id !== tableId &&
|
|
||||||
!hiddenTableIds?.includes(child.id)
|
|
||||||
) {
|
|
||||||
addHiddenTableId(child.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toggleTableVisibility(tableId, !hidden);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hidden || !visibleBySchema ? (
|
{!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" />
|
||||||
@@ -323,13 +300,11 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
toggleTableVisibility,
|
toggleSchemaFilter,
|
||||||
filteredSchemas,
|
toggleTableFilter,
|
||||||
filterSchemas,
|
clearTableIdsFilter,
|
||||||
treeData,
|
setTableIdsFilterEmpty,
|
||||||
hiddenTableIds,
|
databaseWithSchemas,
|
||||||
addHiddenTableId,
|
|
||||||
removeHiddenTableId,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -338,19 +313,15 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
|||||||
(node: TreeNode<NodeType, NodeContext>) => {
|
(node: TreeNode<NodeType, NodeContext>) => {
|
||||||
if (node.type === 'table') {
|
if (node.type === 'table') {
|
||||||
const tableContext = node.context as TableContext;
|
const tableContext = node.context as TableContext;
|
||||||
const tableSchema = tableContext.tableSchema;
|
const isTableVisible = tableContext.visible;
|
||||||
const visibleBySchema = shouldShowTableSchemaBySchemaFilter({
|
|
||||||
tableSchema,
|
|
||||||
filteredSchemas,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only focus if neither table is hidden nor filtered by schema
|
// Only focus if neither table is hidden nor filtered by schema
|
||||||
if (!tableContext.hidden && visibleBySchema) {
|
if (isTableVisible) {
|
||||||
focusOnTable(node.id);
|
focusOnTable(node.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[focusOnTable, filteredSchemas]
|
[focusOnTable]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Animate in on mount and focus search input
|
// Animate in on mount and focus search input
|
||||||
|
@@ -54,10 +54,7 @@ import { Badge } from '@/components/badge/badge';
|
|||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import {
|
import { MIN_TABLE_SIZE } from '@/lib/domain/db-table';
|
||||||
MIN_TABLE_SIZE,
|
|
||||||
shouldShowTablesBySchemaFilter,
|
|
||||||
} from '@/lib/domain/db-table';
|
|
||||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -94,6 +91,10 @@ import { CanvasFilter } from './canvas-filter/canvas-filter';
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { ShowAllButton } from './show-all-button';
|
import { ShowAllButton } from './show-all-button';
|
||||||
import { useIsLostInCanvas } from './hooks/use-is-lost-in-canvas';
|
import { useIsLostInCanvas } from './hooks/use-is-lost-in-canvas';
|
||||||
|
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
const HIGHLIGHTED_EDGE_Z_INDEX = 1;
|
const HIGHLIGHTED_EDGE_Z_INDEX = 1;
|
||||||
const DEFAULT_EDGE_Z_INDEX = 0;
|
const DEFAULT_EDGE_Z_INDEX = 0;
|
||||||
@@ -118,13 +119,8 @@ const initialEdges: EdgeType[] = [];
|
|||||||
|
|
||||||
const tableToTableNode = (
|
const tableToTableNode = (
|
||||||
table: DBTable,
|
table: DBTable,
|
||||||
{
|
filter: DiagramFilter | undefined,
|
||||||
filteredSchemas,
|
databaseType: DatabaseType
|
||||||
hiddenTableIds,
|
|
||||||
}: {
|
|
||||||
filteredSchemas?: string[];
|
|
||||||
hiddenTableIds?: string[];
|
|
||||||
}
|
|
||||||
): TableNodeType => {
|
): TableNodeType => {
|
||||||
// Always use absolute position for now
|
// Always use absolute position for now
|
||||||
const position = { x: table.x, y: table.y };
|
const position = { x: table.x, y: table.y };
|
||||||
@@ -138,9 +134,11 @@ const tableToTableNode = (
|
|||||||
isOverlapping: false,
|
isOverlapping: false,
|
||||||
},
|
},
|
||||||
width: table.width ?? MIN_TABLE_SIZE,
|
width: table.width ?? MIN_TABLE_SIZE,
|
||||||
hidden:
|
hidden: !filterTable({
|
||||||
!shouldShowTablesBySchemaFilter(table, filteredSchemas) ||
|
table: { id: table.id, schema: table.schema },
|
||||||
(hiddenTableIds?.includes(table.id) ?? false),
|
filter,
|
||||||
|
options: { defaultSchema: defaultSchemas[databaseType] },
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -178,7 +176,6 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
removeDependencies,
|
removeDependencies,
|
||||||
getField,
|
getField,
|
||||||
databaseType,
|
databaseType,
|
||||||
filteredSchemas,
|
|
||||||
events,
|
events,
|
||||||
dependencies,
|
dependencies,
|
||||||
readonly,
|
readonly,
|
||||||
@@ -186,7 +183,6 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
updateArea,
|
updateArea,
|
||||||
highlightedCustomType,
|
highlightedCustomType,
|
||||||
highlightCustomTypeId,
|
highlightCustomTypeId,
|
||||||
hiddenTableIds,
|
|
||||||
} = useChartDB();
|
} = useChartDB();
|
||||||
const { showSidePanel } = useLayout();
|
const { showSidePanel } = useLayout();
|
||||||
const { effectiveTheme } = useTheme();
|
const { effectiveTheme } = useTheme();
|
||||||
@@ -204,12 +200,13 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
showFilter,
|
showFilter,
|
||||||
setShowFilter,
|
setShowFilter,
|
||||||
} = useCanvas();
|
} = useCanvas();
|
||||||
|
const { filter } = useDiagramFilter();
|
||||||
|
|
||||||
const [isInitialLoadingNodes, setIsInitialLoadingNodes] = useState(true);
|
const [isInitialLoadingNodes, setIsInitialLoadingNodes] = useState(true);
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState<NodeType>(
|
const [nodes, setNodes, onNodesChange] = useNodesState<NodeType>(
|
||||||
initialTables.map((table) =>
|
initialTables.map((table) =>
|
||||||
tableToTableNode(table, { filteredSchemas, hiddenTableIds })
|
tableToTableNode(table, filter, databaseType)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const [edges, setEdges, onEdgesChange] =
|
const [edges, setEdges, onEdgesChange] =
|
||||||
@@ -223,12 +220,12 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initialNodes = initialTables.map((table) =>
|
const initialNodes = initialTables.map((table) =>
|
||||||
tableToTableNode(table, { filteredSchemas, hiddenTableIds })
|
tableToTableNode(table, filter, databaseType)
|
||||||
);
|
);
|
||||||
if (equal(initialNodes, nodes)) {
|
if (equal(initialNodes, nodes)) {
|
||||||
setIsInitialLoadingNodes(false);
|
setIsInitialLoadingNodes(false);
|
||||||
}
|
}
|
||||||
}, [initialTables, nodes, filteredSchemas, hiddenTableIds]);
|
}, [initialTables, nodes, filter, databaseType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isInitialLoadingNodes) {
|
if (!isInitialLoadingNodes) {
|
||||||
@@ -391,10 +388,7 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
...tables.map((table) => {
|
...tables.map((table) => {
|
||||||
const isOverlapping =
|
const isOverlapping =
|
||||||
(overlapGraph.graph.get(table.id) ?? []).length > 0;
|
(overlapGraph.graph.get(table.id) ?? []).length > 0;
|
||||||
const node = tableToTableNode(table, {
|
const node = tableToTableNode(table, filter, databaseType);
|
||||||
filteredSchemas,
|
|
||||||
hiddenTableIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if table uses the highlighted custom type
|
// Check if table uses the highlighted custom type
|
||||||
let hasHighlightedCustomType = false;
|
let hasHighlightedCustomType = false;
|
||||||
@@ -429,21 +423,30 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
tables,
|
tables,
|
||||||
areas,
|
areas,
|
||||||
setNodes,
|
setNodes,
|
||||||
filteredSchemas,
|
filter,
|
||||||
hiddenTableIds,
|
databaseType,
|
||||||
overlapGraph.lastUpdated,
|
overlapGraph.lastUpdated,
|
||||||
overlapGraph.graph,
|
overlapGraph.graph,
|
||||||
highlightOverlappingTables,
|
highlightOverlappingTables,
|
||||||
highlightedCustomType,
|
highlightedCustomType,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const prevFilteredSchemas = useRef<string[] | undefined>(undefined);
|
const prevFilter = useRef<DiagramFilter | undefined>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!equal(filteredSchemas, prevFilteredSchemas.current)) {
|
if (!equal(filter, prevFilter.current)) {
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
const overlappingTablesInDiagram = findOverlappingTables({
|
const overlappingTablesInDiagram = findOverlappingTables({
|
||||||
tables: tables.filter((table) =>
|
tables: tables.filter((table) =>
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
setOverlapGraph(overlappingTablesInDiagram);
|
setOverlapGraph(overlappingTablesInDiagram);
|
||||||
@@ -453,9 +456,9 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
maxZoom: 0.8,
|
maxZoom: 0.8,
|
||||||
});
|
});
|
||||||
}, 500)();
|
}, 500)();
|
||||||
prevFilteredSchemas.current = filteredSchemas;
|
prevFilter.current = filter;
|
||||||
}
|
}
|
||||||
}, [filteredSchemas, fitView, tables, setOverlapGraph]);
|
}, [filter, fitView, tables, setOverlapGraph, databaseType]);
|
||||||
|
|
||||||
// Handle parent area updates when tables move
|
// Handle parent area updates when tables move
|
||||||
const tablePositions = useMemo(
|
const tablePositions = useMemo(
|
||||||
@@ -1061,7 +1064,16 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
const diagramTables = event.data.diagram.tables ?? [];
|
const diagramTables = event.data.diagram.tables ?? [];
|
||||||
const overlappingTablesInDiagram = findOverlappingTables({
|
const overlappingTablesInDiagram = findOverlappingTables({
|
||||||
tables: diagramTables.filter((table) =>
|
tables: diagramTables.filter((table) =>
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
setOverlapGraph(overlappingTablesInDiagram);
|
setOverlapGraph(overlappingTablesInDiagram);
|
||||||
@@ -1072,8 +1084,9 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
|||||||
setOverlapGraph,
|
setOverlapGraph,
|
||||||
getNode,
|
getNode,
|
||||||
nodes,
|
nodes,
|
||||||
filteredSchemas,
|
filter,
|
||||||
setNodes,
|
setNodes,
|
||||||
|
databaseType,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -15,8 +15,8 @@ import { Button } from '@/components/button/button';
|
|||||||
import { keyboardShortcutsForOS } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
|
import { keyboardShortcutsForOS } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
|
||||||
import { KeyboardShortcutAction } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
|
import { KeyboardShortcutAction } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
|
||||||
import { useCanvas } from '@/hooks/use-canvas';
|
import { useCanvas } from '@/hooks/use-canvas';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
|
||||||
const convertToPercentage = (value: number) => `${Math.round(value * 100)}%`;
|
const convertToPercentage = (value: number) => `${Math.round(value * 100)}%`;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
|
|||||||
const { getZoom, zoomIn, zoomOut, fitView } = useReactFlow();
|
const { getZoom, zoomIn, zoomOut, fitView } = useReactFlow();
|
||||||
const [zoom, setZoom] = useState<string>(convertToPercentage(getZoom()));
|
const [zoom, setZoom] = useState<string>(convertToPercentage(getZoom()));
|
||||||
const { setShowFilter } = useCanvas();
|
const { setShowFilter } = useCanvas();
|
||||||
const { hiddenTableIds } = useChartDB();
|
const { hasActiveFilter } = useDiagramFilter();
|
||||||
|
|
||||||
const toggleFilter = useCallback(() => {
|
const toggleFilter = useCallback(() => {
|
||||||
setShowFilter((prev) => !prev);
|
setShowFilter((prev) => !prev);
|
||||||
@@ -80,8 +80,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
|
|||||||
'transition-all duration-200',
|
'transition-all duration-200',
|
||||||
{
|
{
|
||||||
'bg-pink-500 text-white hover:bg-pink-600 hover:text-white':
|
'bg-pink-500 text-white hover:bg-pink-600 hover:text-white':
|
||||||
(hiddenTableIds ?? []).length >
|
hasActiveFilter,
|
||||||
0,
|
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@@ -1,14 +1,9 @@
|
|||||||
import React, { Suspense, useCallback, useEffect, useRef } from 'react';
|
import React, { Suspense, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
import { Toaster } from '@/components/toast/toaster';
|
import { Toaster } from '@/components/toast/toaster';
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
import { useLayout } from '@/hooks/use-layout';
|
|
||||||
import { useToast } from '@/components/toast/use-toast';
|
|
||||||
import { ToastAction } from '@/components/toast/toast';
|
|
||||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { FullScreenLoaderProvider } from '@/context/full-screen-spinner-context/full-screen-spinner-provider';
|
import { FullScreenLoaderProvider } from '@/context/full-screen-spinner-context/full-screen-spinner-provider';
|
||||||
import { LayoutProvider } from '@/context/layout-context/layout-provider';
|
import { LayoutProvider } from '@/context/layout-context/layout-provider';
|
||||||
import { LocalConfigProvider } from '@/context/local-config-context/local-config-provider';
|
import { LocalConfigProvider } from '@/context/local-config-context/local-config-provider';
|
||||||
@@ -30,6 +25,7 @@ import { HIDE_CHARTDB_CLOUD } from '@/lib/env';
|
|||||||
import { useDiagramLoader } from './use-diagram-loader';
|
import { useDiagramLoader } from './use-diagram-loader';
|
||||||
import { DiffProvider } from '@/context/diff-context/diff-provider';
|
import { DiffProvider } from '@/context/diff-context/diff-provider';
|
||||||
import { TopNavbarMock } from './top-navbar/top-navbar-mock';
|
import { TopNavbarMock } from './top-navbar/top-navbar-mock';
|
||||||
|
import { DiagramFilterProvider } from '@/context/diagram-filter-context/diagram-filter-provider';
|
||||||
|
|
||||||
const OPEN_STAR_US_AFTER_SECONDS = 30;
|
const OPEN_STAR_US_AFTER_SECONDS = 30;
|
||||||
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
||||||
@@ -43,21 +39,11 @@ export const EditorMobileLayoutLazy = React.lazy(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const EditorPageComponent: React.FC = () => {
|
const EditorPageComponent: React.FC = () => {
|
||||||
const { diagramName, currentDiagram, schemas, filteredSchemas } =
|
const { diagramName, currentDiagram } = useChartDB();
|
||||||
useChartDB();
|
|
||||||
const { openSelectSchema, showSidePanel } = useLayout();
|
|
||||||
const { openStarUsDialog } = useDialog();
|
const { openStarUsDialog } = useDialog();
|
||||||
const { diagramId } = useParams<{ diagramId: string }>();
|
|
||||||
const { isMd: isDesktop } = useBreakpoint('md');
|
const { isMd: isDesktop } = useBreakpoint('md');
|
||||||
const {
|
const { starUsDialogLastOpen, setStarUsDialogLastOpen, githubRepoOpened } =
|
||||||
hideMultiSchemaNotification,
|
useLocalConfig();
|
||||||
setHideMultiSchemaNotification,
|
|
||||||
starUsDialogLastOpen,
|
|
||||||
setStarUsDialogLastOpen,
|
|
||||||
githubRepoOpened,
|
|
||||||
} = useLocalConfig();
|
|
||||||
const { toast } = useToast();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { initialDiagram } = useDiagramLoader();
|
const { initialDiagram } = useDiagramLoader();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -85,73 +71,6 @@ const EditorPageComponent: React.FC = () => {
|
|||||||
starUsDialogLastOpen,
|
starUsDialogLastOpen,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const lastDiagramId = useRef<string>('');
|
|
||||||
|
|
||||||
const handleChangeSchema = useCallback(async () => {
|
|
||||||
showSidePanel();
|
|
||||||
if (!isDesktop) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
}
|
|
||||||
openSelectSchema();
|
|
||||||
}, [openSelectSchema, showSidePanel, isDesktop]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (lastDiagramId.current === currentDiagram.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastDiagramId.current = currentDiagram.id;
|
|
||||||
if (schemas.length > 1 && !hideMultiSchemaNotification) {
|
|
||||||
const formattedSchemas = !filteredSchemas
|
|
||||||
? t('multiple_schemas_alert.none')
|
|
||||||
: filteredSchemas
|
|
||||||
.map((filteredSchema) =>
|
|
||||||
schemas.find((schema) => schema.id === filteredSchema)
|
|
||||||
)
|
|
||||||
.map((schema) => `'${schema?.name}'`)
|
|
||||||
.join(', ');
|
|
||||||
toast({
|
|
||||||
duration: Infinity,
|
|
||||||
title: t('multiple_schemas_alert.title'),
|
|
||||||
description: t('multiple_schemas_alert.description', {
|
|
||||||
schemasCount: schemas.length,
|
|
||||||
formattedSchemas,
|
|
||||||
}),
|
|
||||||
variant: 'default',
|
|
||||||
layout: 'column',
|
|
||||||
hideCloseButton: true,
|
|
||||||
className:
|
|
||||||
'top-0 right-0 flex fixed md:max-w-[420px] md:top-4 md:right-4',
|
|
||||||
action: (
|
|
||||||
<div className="flex justify-between gap-1">
|
|
||||||
<div />
|
|
||||||
<ToastAction
|
|
||||||
onClick={() => {
|
|
||||||
handleChangeSchema();
|
|
||||||
setHideMultiSchemaNotification(true);
|
|
||||||
}}
|
|
||||||
altText="Show me the schemas"
|
|
||||||
className="border border-pink-600 bg-pink-600 text-white hover:bg-pink-500"
|
|
||||||
>
|
|
||||||
{t('multiple_schemas_alert.show_me')}
|
|
||||||
</ToastAction>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
schemas,
|
|
||||||
filteredSchemas,
|
|
||||||
toast,
|
|
||||||
currentDiagram.id,
|
|
||||||
diagramId,
|
|
||||||
openSelectSchema,
|
|
||||||
t,
|
|
||||||
handleChangeSchema,
|
|
||||||
hideMultiSchemaNotification,
|
|
||||||
setHideMultiSchemaNotification,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@@ -202,21 +121,23 @@ export const EditorPage: React.FC = () => (
|
|||||||
<RedoUndoStackProvider>
|
<RedoUndoStackProvider>
|
||||||
<DiffProvider>
|
<DiffProvider>
|
||||||
<ChartDBProvider>
|
<ChartDBProvider>
|
||||||
<HistoryProvider>
|
<DiagramFilterProvider>
|
||||||
<ReactFlowProvider>
|
<HistoryProvider>
|
||||||
<CanvasProvider>
|
<ReactFlowProvider>
|
||||||
<ExportImageProvider>
|
<CanvasProvider>
|
||||||
<AlertProvider>
|
<ExportImageProvider>
|
||||||
<DialogProvider>
|
<AlertProvider>
|
||||||
<KeyboardShortcutsProvider>
|
<DialogProvider>
|
||||||
<EditorPageComponent />
|
<KeyboardShortcutsProvider>
|
||||||
</KeyboardShortcutsProvider>
|
<EditorPageComponent />
|
||||||
</DialogProvider>
|
</KeyboardShortcutsProvider>
|
||||||
</AlertProvider>
|
</DialogProvider>
|
||||||
</ExportImageProvider>
|
</AlertProvider>
|
||||||
</CanvasProvider>
|
</ExportImageProvider>
|
||||||
</ReactFlowProvider>
|
</CanvasProvider>
|
||||||
</HistoryProvider>
|
</ReactFlowProvider>
|
||||||
|
</HistoryProvider>
|
||||||
|
</DiagramFilterProvider>
|
||||||
</ChartDBProvider>
|
</ChartDBProvider>
|
||||||
</DiffProvider>
|
</DiffProvider>
|
||||||
</RedoUndoStackProvider>
|
</RedoUndoStackProvider>
|
||||||
|
@@ -34,6 +34,7 @@ import {
|
|||||||
} from '@/lib/domain/db-custom-type';
|
} from '@/lib/domain/db-custom-type';
|
||||||
import { Badge } from '@/components/badge/badge';
|
import { Badge } from '@/components/badge/badge';
|
||||||
import { checkIfCustomTypeUsed } from '../utils';
|
import { checkIfCustomTypeUsed } from '../utils';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
|
||||||
export interface CustomTypeListItemHeaderProps {
|
export interface CustomTypeListItemHeaderProps {
|
||||||
customType: DBCustomType;
|
customType: DBCustomType;
|
||||||
@@ -45,12 +46,11 @@ export const CustomTypeListItemHeader: React.FC<
|
|||||||
const {
|
const {
|
||||||
updateCustomType,
|
updateCustomType,
|
||||||
removeCustomType,
|
removeCustomType,
|
||||||
schemas,
|
|
||||||
filteredSchemas,
|
|
||||||
highlightedCustomType,
|
highlightedCustomType,
|
||||||
highlightCustomTypeId,
|
highlightCustomTypeId,
|
||||||
tables,
|
tables,
|
||||||
} = useChartDB();
|
} = useChartDB();
|
||||||
|
const { schemasDisplayed } = useDiagramFilter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [editMode, setEditMode] = React.useState(false);
|
const [editMode, setEditMode] = React.useState(false);
|
||||||
const [customTypeName, setCustomTypeName] = React.useState(customType.name);
|
const [customTypeName, setCustomTypeName] = React.useState(customType.name);
|
||||||
@@ -161,11 +161,11 @@ export const CustomTypeListItemHeader: React.FC<
|
|||||||
isHighlighted,
|
isHighlighted,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let schemaToDisplay;
|
const schemaToDisplay = useMemo(() => {
|
||||||
|
if (schemasDisplayed.length > 1) {
|
||||||
if (schemas.length > 1 && !!filteredSchemas && filteredSchemas.length > 1) {
|
return customType.schema;
|
||||||
schemaToDisplay = customType.schema;
|
}
|
||||||
}
|
}, [customType.schema, schemasDisplayed.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group flex h-11 flex-1 items-center justify-between gap-1 overflow-hidden">
|
<div className="group flex h-11 flex-1 items-center justify-between gap-1 overflow-hidden">
|
||||||
|
@@ -13,13 +13,16 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/tooltip/tooltip';
|
} from '@/components/tooltip/tooltip';
|
||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import { shouldShowDependencyBySchemaFilter } from '@/lib/domain/db-dependency';
|
|
||||||
import { useLayout } from '@/hooks/use-layout';
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
import { filterDependency } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
export interface DependenciesSectionProps {}
|
export interface DependenciesSectionProps {}
|
||||||
|
|
||||||
export const DependenciesSection: React.FC<DependenciesSectionProps> = () => {
|
export const DependenciesSection: React.FC<DependenciesSectionProps> = () => {
|
||||||
const { dependencies, filteredSchemas, getTable } = useChartDB();
|
const { dependencies, getTable, databaseType } = useChartDB();
|
||||||
|
const { filter } = useDiagramFilter();
|
||||||
const [filterText, setFilterText] = React.useState('');
|
const [filterText, setFilterText] = React.useState('');
|
||||||
const { closeAllDependenciesInSidebar } = useLayout();
|
const { closeAllDependenciesInSidebar } = useLayout();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -44,12 +47,26 @@ export const DependenciesSection: React.FC<DependenciesSectionProps> = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterSchema: (dependency: DBDependency) => boolean = (
|
const filterDependencies: (dependency: DBDependency) => boolean = (
|
||||||
dependency
|
dependency
|
||||||
) => shouldShowDependencyBySchemaFilter(dependency, filteredSchemas);
|
) =>
|
||||||
|
filterDependency({
|
||||||
|
tableA: {
|
||||||
|
id: dependency.tableId,
|
||||||
|
schema: dependency.schema,
|
||||||
|
},
|
||||||
|
tableB: {
|
||||||
|
id: dependency.dependentTableId,
|
||||||
|
schema: dependency.dependentSchema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return dependencies
|
return dependencies
|
||||||
.filter(filterSchema)
|
.filter(filterDependencies)
|
||||||
.filter(filterName)
|
.filter(filterName)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const dependentTableA = getTable(a.dependentTableId);
|
const dependentTableA = getTable(a.dependentTableId);
|
||||||
@@ -60,7 +77,7 @@ export const DependenciesSection: React.FC<DependenciesSectionProps> = () => {
|
|||||||
`${dependentTableB?.name}${tableB?.name}`
|
`${dependentTableB?.name}${tableB?.name}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [dependencies, filterText, filteredSchemas, getTable]);
|
}, [dependencies, filterText, filter, getTable, databaseType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-1 flex-col overflow-hidden px-2">
|
<section className="flex flex-1 flex-col overflow-hidden px-2">
|
||||||
|
@@ -5,7 +5,6 @@ import { Input } from '@/components/input/input';
|
|||||||
import { RelationshipList } from './relationship-list/relationship-list';
|
import { RelationshipList } from './relationship-list/relationship-list';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
import { shouldShowRelationshipBySchemaFilter } from '@/lib/domain/db-relationship';
|
|
||||||
import { useLayout } from '@/hooks/use-layout';
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
import { EmptyState } from '@/components/empty-state/empty-state';
|
import { EmptyState } from '@/components/empty-state/empty-state';
|
||||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
||||||
@@ -16,11 +15,15 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/tooltip/tooltip';
|
} from '@/components/tooltip/tooltip';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
import { filterRelationship } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
export interface RelationshipsSectionProps {}
|
export interface RelationshipsSectionProps {}
|
||||||
|
|
||||||
export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
||||||
const { relationships, filteredSchemas } = useChartDB();
|
const { relationships, databaseType } = useChartDB();
|
||||||
|
const { filter } = useDiagramFilter();
|
||||||
const [filterText, setFilterText] = React.useState('');
|
const [filterText, setFilterText] = React.useState('');
|
||||||
const { closeAllRelationshipsInSidebar } = useLayout();
|
const { closeAllRelationshipsInSidebar } = useLayout();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -34,13 +37,26 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
|||||||
!filterText?.trim?.() ||
|
!filterText?.trim?.() ||
|
||||||
relationship.name.toLowerCase().includes(filterText.toLowerCase());
|
relationship.name.toLowerCase().includes(filterText.toLowerCase());
|
||||||
|
|
||||||
const filterSchema: (relationship: DBRelationship) => boolean = (
|
const filterRelationships: (relationship: DBRelationship) => boolean = (
|
||||||
relationship
|
relationship
|
||||||
) =>
|
) =>
|
||||||
shouldShowRelationshipBySchemaFilter(relationship, filteredSchemas);
|
filterRelationship({
|
||||||
|
tableA: {
|
||||||
|
id: relationship.sourceTableId,
|
||||||
|
schema: relationship.sourceSchema,
|
||||||
|
},
|
||||||
|
tableB: {
|
||||||
|
id: relationship.targetTableId,
|
||||||
|
schema: relationship.targetSchema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return relationships.filter(filterSchema).filter(filterName);
|
return relationships.filter(filterRelationships).filter(filterName);
|
||||||
}, [relationships, filterText, filteredSchemas]);
|
}, [relationships, filterText, filter, databaseType]);
|
||||||
|
|
||||||
const handleCreateRelationship = useCallback(async () => {
|
const handleCreateRelationship = useCallback(async () => {
|
||||||
setFilterText('');
|
setFilterText('');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -12,8 +12,6 @@ import { RelationshipsSection } from './relationships-section/relationships-sect
|
|||||||
import { useLayout } from '@/hooks/use-layout';
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
import type { SidebarSection } from '@/context/layout-context/layout-context';
|
import type { SidebarSection } from '@/context/layout-context/layout-context';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { SelectBoxOption } from '@/components/select-box/select-box';
|
|
||||||
import { SelectBox } from '@/components/select-box/select-box';
|
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { DependenciesSection } from './dependencies-section/dependencies-section';
|
import { DependenciesSection } from './dependencies-section/dependencies-section';
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
@@ -25,69 +23,12 @@ export interface SidePanelProps {}
|
|||||||
|
|
||||||
export const SidePanel: React.FC<SidePanelProps> = () => {
|
export const SidePanel: React.FC<SidePanelProps> = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { schemas, filterSchemas, filteredSchemas, databaseType } =
|
const { databaseType } = useChartDB();
|
||||||
useChartDB();
|
const { selectSidebarSection, selectedSidebarSection } = useLayout();
|
||||||
const {
|
|
||||||
selectSidebarSection,
|
|
||||||
selectedSidebarSection,
|
|
||||||
isSelectSchemaOpen,
|
|
||||||
openSelectSchema,
|
|
||||||
closeSelectSchema,
|
|
||||||
} = useLayout();
|
|
||||||
const { isMd: isDesktop } = useBreakpoint('md');
|
const { isMd: isDesktop } = useBreakpoint('md');
|
||||||
|
|
||||||
const schemasOptions: SelectBoxOption[] = useMemo(
|
|
||||||
() =>
|
|
||||||
schemas.map(
|
|
||||||
(schema): SelectBoxOption => ({
|
|
||||||
label: schema.name,
|
|
||||||
value: schema.id,
|
|
||||||
description: `(${schema.tableCount} tables)`,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
[schemas]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setIsSelectSchemaOpen = useCallback(
|
|
||||||
(open: boolean) => {
|
|
||||||
if (open) {
|
|
||||||
openSelectSchema();
|
|
||||||
} else {
|
|
||||||
closeSelectSchema();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[openSelectSchema, closeSelectSchema]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="flex h-full flex-col overflow-hidden">
|
<aside className="flex h-full flex-col overflow-hidden">
|
||||||
{schemasOptions.length > 0 ? (
|
|
||||||
<div className="flex items-center justify-center border-b pl-3 pt-0.5">
|
|
||||||
<div className="shrink-0 text-sm font-semibold">
|
|
||||||
{t('side_panel.schema')}
|
|
||||||
</div>
|
|
||||||
<div className="flex min-w-0 flex-1">
|
|
||||||
<SelectBox
|
|
||||||
oneLine
|
|
||||||
className="w-full rounded-none border-none"
|
|
||||||
selectAll
|
|
||||||
deselectAll
|
|
||||||
options={schemasOptions}
|
|
||||||
value={filteredSchemas ?? []}
|
|
||||||
onChange={(values) => {
|
|
||||||
filterSchemas(values as string[]);
|
|
||||||
}}
|
|
||||||
placeholder={t('side_panel.filter_by_schema')}
|
|
||||||
inputPlaceholder={t('side_panel.search_schema')}
|
|
||||||
emptyPlaceholder={t('side_panel.no_schemas_found')}
|
|
||||||
multiple
|
|
||||||
open={isSelectSchemaOpen}
|
|
||||||
onOpenChange={setIsSelectSchemaOpen}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{!isDesktop ? (
|
{!isDesktop ? (
|
||||||
<div className="flex justify-center border-b pt-0.5">
|
<div className="flex justify-center border-b pt-0.5">
|
||||||
<Select
|
<Select
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
CircleDotDashed,
|
CircleDotDashed,
|
||||||
GripVertical,
|
GripVertical,
|
||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
import { cloneTable } from '@/lib/clone';
|
import { cloneTable } from '@/lib/clone';
|
||||||
import type { DBSchema } from '@/lib/domain';
|
import type { DBSchema } from '@/lib/domain';
|
||||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
|
||||||
export interface TableListItemHeaderProps {
|
export interface TableListItemHeaderProps {
|
||||||
table: DBTable;
|
table: DBTable;
|
||||||
@@ -55,9 +56,9 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
|||||||
createField,
|
createField,
|
||||||
createTable,
|
createTable,
|
||||||
schemas,
|
schemas,
|
||||||
filteredSchemas,
|
|
||||||
databaseType,
|
databaseType,
|
||||||
} = useChartDB();
|
} = useChartDB();
|
||||||
|
const { schemasDisplayed } = useDiagramFilter();
|
||||||
const { openTableSchemaDialog } = useDialog();
|
const { openTableSchemaDialog } = useDialog();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { fitView, setNodes } = useReactFlow();
|
const { fitView, setNodes } = useReactFlow();
|
||||||
@@ -265,13 +266,13 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let schemaToDisplay;
|
const schemaToDisplay = useMemo(() => {
|
||||||
|
if (schemasDisplayed.length > 1) {
|
||||||
|
return table.schema;
|
||||||
|
}
|
||||||
|
}, [table.schema, schemasDisplayed.length]);
|
||||||
|
|
||||||
if (schemas.length > 1 && !!filteredSchemas && filteredSchemas.length > 1) {
|
useEffect(() => {
|
||||||
schemaToDisplay = table.schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (table.name.trim()) {
|
if (table.name.trim()) {
|
||||||
setTableName(table.name.trim());
|
setTableName(table.name.trim());
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import { Button } from '@/components/button/button';
|
|||||||
import { Table, List, X, Code } from 'lucide-react';
|
import { Table, List, X, Code } from 'lucide-react';
|
||||||
import { Input } from '@/components/input/input';
|
import { Input } from '@/components/input/input';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
|
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { useLayout } from '@/hooks/use-layout';
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
import { EmptyState } from '@/components/empty-state/empty-state';
|
import { EmptyState } from '@/components/empty-state/empty-state';
|
||||||
@@ -21,11 +20,15 @@ import { TableDBML } from './table-dbml/table-dbml';
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { getOperatingSystem } from '@/lib/utils';
|
import { getOperatingSystem } from '@/lib/utils';
|
||||||
import type { DBSchema } from '@/lib/domain';
|
import type { DBSchema } from '@/lib/domain';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
export interface TablesSectionProps {}
|
export interface TablesSectionProps {}
|
||||||
|
|
||||||
export const TablesSection: React.FC<TablesSectionProps> = () => {
|
export const TablesSection: React.FC<TablesSectionProps> = () => {
|
||||||
const { createTable, tables, filteredSchemas, schemas } = useChartDB();
|
const { createTable, tables, databaseType } = useChartDB();
|
||||||
|
const { filter, schemasDisplayed } = useDiagramFilter();
|
||||||
const { openTableSchemaDialog } = useDialog();
|
const { openTableSchemaDialog } = useDialog();
|
||||||
const viewport = useViewport();
|
const viewport = useViewport();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -39,11 +42,20 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
|
|||||||
!filterText?.trim?.() ||
|
!filterText?.trim?.() ||
|
||||||
table.name.toLowerCase().includes(filterText.toLowerCase());
|
table.name.toLowerCase().includes(filterText.toLowerCase());
|
||||||
|
|
||||||
const filterSchema: (table: DBTable) => boolean = (table) =>
|
const filterTables: (table: DBTable) => boolean = (table) =>
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas);
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return tables.filter(filterSchema).filter(filterTableName);
|
return tables.filter(filterTables).filter(filterTableName);
|
||||||
}, [tables, filterText, filteredSchemas]);
|
}, [tables, filterText, filter, databaseType]);
|
||||||
|
|
||||||
const createTableWithLocation = useCallback(
|
const createTableWithLocation = useCallback(
|
||||||
async ({ schema }: { schema?: DBSchema }) => {
|
async ({ schema }: { schema?: DBSchema }) => {
|
||||||
@@ -71,25 +83,20 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
|
|||||||
const handleCreateTable = useCallback(async () => {
|
const handleCreateTable = useCallback(async () => {
|
||||||
setFilterText('');
|
setFilterText('');
|
||||||
|
|
||||||
if ((filteredSchemas?.length ?? 0) > 1) {
|
if (schemasDisplayed.length > 1) {
|
||||||
openTableSchemaDialog({
|
openTableSchemaDialog({
|
||||||
onConfirm: createTableWithLocation,
|
onConfirm: createTableWithLocation,
|
||||||
schemas: schemas.filter((schema) =>
|
schemas: schemasDisplayed,
|
||||||
filteredSchemas?.includes(schema.id)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const schema =
|
const schema =
|
||||||
filteredSchemas?.length === 1
|
schemasDisplayed.length === 1 ? schemasDisplayed[0] : undefined;
|
||||||
? schemas.find((s) => s.id === filteredSchemas[0])
|
|
||||||
: undefined;
|
|
||||||
createTableWithLocation({ schema });
|
createTableWithLocation({ schema });
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
createTableWithLocation,
|
createTableWithLocation,
|
||||||
filteredSchemas,
|
schemasDisplayed,
|
||||||
openTableSchemaDialog,
|
openTableSchemaDialog,
|
||||||
schemas,
|
|
||||||
setFilterText,
|
setFilterText,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user