mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-22 23:01: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="robots" content="max-image-preview:large" />
|
||||
<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.gstatic.com" crossorigin />
|
||||
<link
|
||||
|
@@ -1,22 +1,23 @@
|
||||
import React, { type ReactNode, useCallback, useState } from 'react';
|
||||
import { canvasContext } from './canvas-context';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import {
|
||||
adjustTablePositions,
|
||||
shouldShowTablesBySchemaFilter,
|
||||
} from '@/lib/domain/db-table';
|
||||
import { adjustTablePositions } from '@/lib/domain/db-table';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
|
||||
import type { Graph } 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 {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
const { tables, relationships, updateTablesState, filteredSchemas } =
|
||||
const { tables, relationships, updateTablesState, databaseType } =
|
||||
useChartDB();
|
||||
const { filter } = useDiagramFilter();
|
||||
const { fitView } = useReactFlow();
|
||||
const [overlapGraph, setOverlapGraph] =
|
||||
useState<Graph<string>>(createGraph());
|
||||
@@ -32,9 +33,18 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
const newTables = adjustTablePositions({
|
||||
relationships,
|
||||
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({
|
||||
@@ -69,7 +79,14 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
});
|
||||
}, 500);
|
||||
},
|
||||
[filteredSchemas, relationships, tables, updateTablesState, fitView]
|
||||
[
|
||||
filter,
|
||||
relationships,
|
||||
tables,
|
||||
updateTablesState,
|
||||
fitView,
|
||||
databaseType,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@@ -81,9 +81,6 @@ export interface ChartDBContext {
|
||||
highlightedCustomType?: DBCustomType;
|
||||
highlightCustomTypeId: (id?: string) => void;
|
||||
|
||||
filteredSchemas?: string[];
|
||||
filterSchemas: (schemaIds: string[]) => void;
|
||||
|
||||
// General operations
|
||||
updateDiagramId: (id: string) => Promise<void>;
|
||||
updateDiagramName: (
|
||||
@@ -284,11 +281,6 @@ export interface ChartDBContext {
|
||||
customType: Partial<DBCustomType>,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
|
||||
// Filters
|
||||
hiddenTableIds?: string[];
|
||||
addHiddenTableId: (tableId: string) => Promise<void>;
|
||||
removeHiddenTableId: (tableId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const chartDBContext = createContext<ChartDBContext>({
|
||||
@@ -302,8 +294,6 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
customTypes: [],
|
||||
schemas: [],
|
||||
highlightCustomTypeId: emptyFn,
|
||||
filteredSchemas: [],
|
||||
filterSchemas: emptyFn,
|
||||
currentDiagram: {
|
||||
id: '',
|
||||
name: '',
|
||||
@@ -386,9 +376,4 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
removeCustomType: emptyFn,
|
||||
removeCustomTypes: 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 { deepCopy, generateId } from '@/lib/utils';
|
||||
import { randomColor } from '@/lib/colors';
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
databasesWithSchemas,
|
||||
schemaNameToSchemaId,
|
||||
} from '@/lib/domain/db-schema';
|
||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import { useEventEmitter } from 'ahooks';
|
||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||
@@ -29,7 +28,6 @@ import {
|
||||
DBCustomTypeKind,
|
||||
type DBCustomType,
|
||||
} from '@/lib/domain/db-custom-type';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
|
||||
export interface ChartDBProviderProps {
|
||||
diagram?: Diagram;
|
||||
@@ -43,14 +41,9 @@ export const ChartDBProvider: React.FC<
|
||||
const dbStorage = useStorage();
|
||||
let db = dbStorage;
|
||||
const events = useEventEmitter<ChartDBEvent>();
|
||||
const { setSchemasFilter, schemasFilter } = useLocalConfig();
|
||||
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
||||
useRedoUndoStack();
|
||||
const {
|
||||
getHiddenTablesForDiagram,
|
||||
hideTableForDiagram,
|
||||
unhideTableForDiagram,
|
||||
} = useConfig();
|
||||
|
||||
const [diagramId, setDiagramId] = useState('');
|
||||
const [diagramName, setDiagramName] = useState('');
|
||||
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
||||
@@ -72,7 +65,7 @@ export const ChartDBProvider: React.FC<
|
||||
const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
|
||||
diagram?.customTypes ?? []
|
||||
);
|
||||
const [hiddenTableIds, setHiddenTableIds] = useState<string[]>([]);
|
||||
|
||||
const { events: diffEvents } = useDiff();
|
||||
|
||||
const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
|
||||
@@ -96,14 +89,6 @@ export const ChartDBProvider: React.FC<
|
||||
|
||||
diffEvents.useSubscription(diffCalculatedHandler);
|
||||
|
||||
// Sync hiddenTableIds with config
|
||||
useEffect(() => {
|
||||
if (diagramId) {
|
||||
const hiddenTables = getHiddenTablesForDiagram(diagramId);
|
||||
setHiddenTableIds(hiddenTables);
|
||||
}
|
||||
}, [diagramId, getHiddenTablesForDiagram]);
|
||||
|
||||
const defaultSchemaName = defaultSchemas[databaseType];
|
||||
|
||||
const readonly = useMemo(
|
||||
@@ -141,34 +126,6 @@ export const ChartDBProvider: React.FC<
|
||||
[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(
|
||||
() => ({
|
||||
id: diagramId,
|
||||
@@ -1125,12 +1082,15 @@ export const ChartDBProvider: React.FC<
|
||||
|
||||
const sourceFieldName = sourceField?.name ?? '';
|
||||
|
||||
const targetTable = getTable(targetTableId);
|
||||
const targetTableSchema = targetTable?.schema;
|
||||
|
||||
const relationship: DBRelationship = {
|
||||
id: generateId(),
|
||||
name: `${sourceTableName}_${sourceFieldName}_fk`,
|
||||
sourceSchema: sourceTable?.schema,
|
||||
sourceTableId,
|
||||
targetSchema: sourceTable?.schema,
|
||||
targetSchema: targetTableSchema,
|
||||
targetTableId,
|
||||
sourceFieldId,
|
||||
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 (
|
||||
<chartDBContext.Provider
|
||||
value={{
|
||||
@@ -1794,10 +1731,8 @@ export const ChartDBProvider: React.FC<
|
||||
areas,
|
||||
currentDiagram,
|
||||
schemas,
|
||||
filteredSchemas,
|
||||
events,
|
||||
readonly,
|
||||
filterSchemas,
|
||||
updateDiagramData,
|
||||
updateDiagramId,
|
||||
updateDiagramName,
|
||||
@@ -1855,9 +1790,6 @@ export const ChartDBProvider: React.FC<
|
||||
removeCustomType,
|
||||
removeCustomTypes,
|
||||
updateCustomType,
|
||||
hiddenTableIds,
|
||||
addHiddenTableId,
|
||||
removeHiddenTableId,
|
||||
highlightCustomTypeId,
|
||||
highlightedCustomType,
|
||||
}}
|
||||
|
@@ -8,23 +8,9 @@ export interface ConfigContext {
|
||||
config?: Partial<ChartDBConfig>;
|
||||
updateFn?: (config: ChartDBConfig) => ChartDBConfig;
|
||||
}) => 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>({
|
||||
config: undefined,
|
||||
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 { useStorage } from '@/hooks/use-storage';
|
||||
@@ -8,7 +8,7 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { getConfig, updateConfig: updateDataConfig } = useStorage();
|
||||
const [config, setConfig] = React.useState<ChartDBConfig | undefined>();
|
||||
const [config, setConfig] = useState<ChartDBConfig | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const loadConfig = async () => {
|
||||
@@ -44,84 +44,11 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
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 (
|
||||
<ConfigContext.Provider
|
||||
value={{
|
||||
config,
|
||||
updateConfig,
|
||||
getHiddenTablesForDiagram,
|
||||
setHiddenTablesForDiagram,
|
||||
hideTableForDiagram,
|
||||
unhideTableForDiagram,
|
||||
}}
|
||||
>
|
||||
{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;
|
||||
showSidePanel: () => void;
|
||||
toggleSidePanel: () => void;
|
||||
|
||||
isSelectSchemaOpen: boolean;
|
||||
openSelectSchema: () => void;
|
||||
closeSelectSchema: () => void;
|
||||
}
|
||||
|
||||
export const layoutContext = createContext<LayoutContext>({
|
||||
@@ -70,8 +66,4 @@ export const layoutContext = createContext<LayoutContext>({
|
||||
hideSidePanel: emptyFn,
|
||||
showSidePanel: emptyFn,
|
||||
toggleSidePanel: emptyFn,
|
||||
|
||||
isSelectSchemaOpen: false,
|
||||
openSelectSchema: emptyFn,
|
||||
closeSelectSchema: emptyFn,
|
||||
});
|
||||
|
@@ -23,8 +23,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
React.useState<SidebarSection>('tables');
|
||||
const [isSidePanelShowed, setIsSidePanelShowed] =
|
||||
React.useState<boolean>(isDesktop);
|
||||
const [isSelectSchemaOpen, setIsSelectSchemaOpen] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
||||
() => setOpenedTableInSidebar('');
|
||||
@@ -88,11 +86,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setOpenedTableInSidebar(customTypeId);
|
||||
};
|
||||
|
||||
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
|
||||
setIsSelectSchemaOpen(true);
|
||||
|
||||
const closeSelectSchema: LayoutContext['closeSelectSchema'] = () =>
|
||||
setIsSelectSchemaOpen(false);
|
||||
return (
|
||||
<layoutContext.Provider
|
||||
value={{
|
||||
@@ -108,9 +101,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
hideSidePanel,
|
||||
showSidePanel,
|
||||
toggleSidePanel,
|
||||
isSelectSchemaOpen,
|
||||
openSelectSchema,
|
||||
closeSelectSchema,
|
||||
openedDependencyInSidebar,
|
||||
openDependencyFromSidebar,
|
||||
closeAllDependenciesInSidebar,
|
||||
|
@@ -4,8 +4,6 @@ import type { Theme } from '../theme-context/theme-context';
|
||||
|
||||
export type ScrollAction = 'pan' | 'zoom';
|
||||
|
||||
export type SchemasFilter = Record<string, string[]>;
|
||||
|
||||
export interface LocalConfigContext {
|
||||
theme: Theme;
|
||||
setTheme: (theme: Theme) => void;
|
||||
@@ -13,20 +11,12 @@ export interface LocalConfigContext {
|
||||
scrollAction: ScrollAction;
|
||||
setScrollAction: (action: ScrollAction) => void;
|
||||
|
||||
schemasFilter: SchemasFilter;
|
||||
setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>;
|
||||
|
||||
showCardinality: boolean;
|
||||
setShowCardinality: (showCardinality: boolean) => void;
|
||||
|
||||
showFieldAttributes: boolean;
|
||||
setShowFieldAttributes: (showFieldAttributes: boolean) => void;
|
||||
|
||||
hideMultiSchemaNotification: boolean;
|
||||
setHideMultiSchemaNotification: (
|
||||
hideMultiSchemaNotification: boolean
|
||||
) => void;
|
||||
|
||||
githubRepoOpened: boolean;
|
||||
setGithubRepoOpened: (githubRepoOpened: boolean) => void;
|
||||
|
||||
@@ -47,18 +37,12 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
||||
scrollAction: 'pan',
|
||||
setScrollAction: emptyFn,
|
||||
|
||||
schemasFilter: {},
|
||||
setSchemasFilter: emptyFn,
|
||||
|
||||
showCardinality: true,
|
||||
setShowCardinality: emptyFn,
|
||||
|
||||
showFieldAttributes: true,
|
||||
setShowFieldAttributes: emptyFn,
|
||||
|
||||
hideMultiSchemaNotification: false,
|
||||
setHideMultiSchemaNotification: emptyFn,
|
||||
|
||||
githubRepoOpened: false,
|
||||
setGithubRepoOpened: emptyFn,
|
||||
|
||||
|
@@ -1,14 +1,12 @@
|
||||
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 type { Theme } from '../theme-context/theme-context';
|
||||
|
||||
const themeKey = 'theme';
|
||||
const scrollActionKey = 'scroll_action';
|
||||
const schemasFilterKey = 'schemas_filter';
|
||||
const showCardinalityKey = 'show_cardinality';
|
||||
const showFieldAttributesKey = 'show_field_attributes';
|
||||
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
|
||||
const githubRepoOpenedKey = 'github_repo_opened';
|
||||
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
||||
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
|
||||
@@ -25,12 +23,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
(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>(
|
||||
(localStorage.getItem(showCardinalityKey) || 'true') === 'true'
|
||||
);
|
||||
@@ -40,12 +32,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
(localStorage.getItem(showFieldAttributesKey) || 'true') === 'true'
|
||||
);
|
||||
|
||||
const [hideMultiSchemaNotification, setHideMultiSchemaNotification] =
|
||||
React.useState<boolean>(
|
||||
(localStorage.getItem(hideMultiSchemaNotificationKey) ||
|
||||
'false') === 'true'
|
||||
);
|
||||
|
||||
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
|
||||
(localStorage.getItem(githubRepoOpenedKey) || 'false') === 'true'
|
||||
);
|
||||
@@ -77,13 +63,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
||||
}, [githubRepoOpened]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(
|
||||
hideMultiSchemaNotificationKey,
|
||||
hideMultiSchemaNotification.toString()
|
||||
);
|
||||
}, [hideMultiSchemaNotification]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(themeKey, theme);
|
||||
}, [theme]);
|
||||
@@ -92,10 +71,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
localStorage.setItem(scrollActionKey, scrollAction);
|
||||
}, [scrollAction]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(schemasFilterKey, JSON.stringify(schemasFilter));
|
||||
}, [schemasFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(showCardinalityKey, showCardinality.toString());
|
||||
}, [showCardinality]);
|
||||
@@ -121,14 +96,10 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setTheme,
|
||||
scrollAction,
|
||||
setScrollAction,
|
||||
schemasFilter,
|
||||
setSchemasFilter,
|
||||
showCardinality,
|
||||
setShowCardinality,
|
||||
showFieldAttributes,
|
||||
setShowFieldAttributes,
|
||||
hideMultiSchemaNotification,
|
||||
setHideMultiSchemaNotification,
|
||||
setGithubRepoOpened,
|
||||
githubRepoOpened,
|
||||
starUsDialogLastOpen,
|
||||
|
@@ -7,12 +7,21 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||
import type { Area } from '@/lib/domain/area';
|
||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||
|
||||
export interface StorageContext {
|
||||
// Config operations
|
||||
getConfig: () => Promise<ChartDBConfig | undefined>;
|
||||
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
|
||||
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
||||
listDiagrams: (options?: {
|
||||
@@ -132,6 +141,10 @@ export const storageInitialValue: StorageContext = {
|
||||
getConfig: emptyFn,
|
||||
updateConfig: emptyFn,
|
||||
|
||||
getDiagramFilter: emptyFn,
|
||||
updateDiagramFilter: emptyFn,
|
||||
deleteDiagramFilter: emptyFn,
|
||||
|
||||
addDiagram: emptyFn,
|
||||
listDiagrams: emptyFn,
|
||||
getDiagram: emptyFn,
|
||||
|
@@ -10,6 +10,7 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||
import type { Area } from '@/lib/domain/area';
|
||||
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> = ({
|
||||
children,
|
||||
@@ -44,6 +45,10 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
ChartDBConfig & { id: number },
|
||||
'id' // primary key "id" (for the typings only)
|
||||
>;
|
||||
diagram_filters: EntityTable<
|
||||
DiagramFilter & { diagramId: string },
|
||||
'diagramId' // primary key "id" (for the typings only)
|
||||
>;
|
||||
};
|
||||
|
||||
// Schema declaration:
|
||||
@@ -190,6 +195,27 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
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 () => {
|
||||
const config = await dexieDB.config.get(1);
|
||||
|
||||
@@ -217,6 +243,32 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[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(
|
||||
async ({ diagramId, table }) => {
|
||||
await db.db_tables.add({
|
||||
@@ -756,6 +808,9 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
deleteCustomType,
|
||||
listCustomTypes,
|
||||
deleteDiagramCustomTypes,
|
||||
getDiagramFilter,
|
||||
updateDiagramFilter,
|
||||
deleteDiagramFilter,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@@ -20,12 +20,18 @@ import {
|
||||
} from '@/lib/data/export-metadata/export-sql-script';
|
||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
|
||||
import { Annoyed, Sparkles } from 'lucide-react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||
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 {
|
||||
targetDatabaseType: DatabaseType;
|
||||
@@ -36,7 +42,8 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
targetDatabaseType,
|
||||
}) => {
|
||||
const { closeExportSQLDialog } = useDialog();
|
||||
const { currentDiagram, filteredSchemas } = useChartDB();
|
||||
const { currentDiagram } = useChartDB();
|
||||
const { filter } = useDiagramFilter();
|
||||
const { t } = useTranslation();
|
||||
const [script, setScript] = React.useState<string>();
|
||||
const [error, setError] = React.useState<boolean>(false);
|
||||
@@ -48,7 +55,16 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
const filteredDiagram: Diagram = {
|
||||
...currentDiagram,
|
||||
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) => {
|
||||
const sourceTable = currentDiagram.tables?.find(
|
||||
@@ -60,11 +76,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
return (
|
||||
sourceTable &&
|
||||
targetTable &&
|
||||
shouldShowTablesBySchemaFilter(
|
||||
sourceTable,
|
||||
filteredSchemas
|
||||
) &&
|
||||
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
|
||||
filterRelationship({
|
||||
tableA: {
|
||||
id: sourceTable.id,
|
||||
schema: sourceTable.schema,
|
||||
},
|
||||
tableB: {
|
||||
id: targetTable.id,
|
||||
schema: targetTable.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
dependencies: currentDiagram.dependencies?.filter((dep) => {
|
||||
@@ -77,11 +102,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
return (
|
||||
table &&
|
||||
dependentTable &&
|
||||
shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
|
||||
shouldShowTablesBySchemaFilter(
|
||||
dependentTable,
|
||||
filteredSchemas
|
||||
)
|
||||
filterDependency({
|
||||
tableA: {
|
||||
id: table.id,
|
||||
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,
|
||||
});
|
||||
}
|
||||
}, [targetDatabaseType, currentDiagram, filteredSchemas]);
|
||||
}, [targetDatabaseType, currentDiagram, filter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialog.open) {
|
||||
|
@@ -28,6 +28,7 @@ import {
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import { Label } from '@/components/label/label';
|
||||
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||
|
||||
export interface TableSchemaDialogProps extends BaseDialogProps {
|
||||
table?: DBTable;
|
||||
@@ -44,7 +45,8 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||
allowSchemaCreation = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { databaseType, filteredSchemas, filterSchemas } = useChartDB();
|
||||
const { databaseType } = useChartDB();
|
||||
const { addSchemaIfFiltered } = useDiagramFilter();
|
||||
const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
|
||||
table?.schema
|
||||
? schemaNameToSchemaId(table.schema)
|
||||
@@ -112,18 +114,14 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||
onConfirm({ schema });
|
||||
}
|
||||
|
||||
filterSchemas([
|
||||
...(filteredSchemas ?? schemas.map((s) => s.id)),
|
||||
createdSchemaId,
|
||||
]);
|
||||
addSchemaIfFiltered(createdSchemaId);
|
||||
}, [
|
||||
onConfirm,
|
||||
selectedSchemaId,
|
||||
schemas,
|
||||
isCreatingNew,
|
||||
newSchemaName,
|
||||
filteredSchemas,
|
||||
filterSchemas,
|
||||
addSchemaIfFiltered,
|
||||
]);
|
||||
|
||||
const schemaOptions: SelectBoxOption[] = useMemo(
|
||||
|
@@ -72,15 +72,6 @@ export const ar: LanguageTranslation = {
|
||||
cancel: 'إلغاء',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'مخططات متعددة',
|
||||
description:
|
||||
'{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'لا شيء',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'فشل النسخ',
|
||||
@@ -115,10 +106,6 @@ export const ar: LanguageTranslation = {
|
||||
copied: '!تم النسخ',
|
||||
|
||||
side_panel: {
|
||||
schema: ':المخطط',
|
||||
filter_by_schema: 'تصفية حسب المخطط',
|
||||
search_schema: '...بحث في المخطط',
|
||||
no_schemas_found: '.لم يتم العثور على مخططات',
|
||||
view_all_options: '...عرض جميع الخيارات',
|
||||
tables_section: {
|
||||
tables: 'الجداول',
|
||||
|
@@ -73,15 +73,6 @@ export const bn: LanguageTranslation = {
|
||||
cancel: 'বাতিল করুন',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'বহু স্কিমা',
|
||||
description:
|
||||
'{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'কিছুই না',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'কপি ব্যর্থ হয়েছে',
|
||||
@@ -116,10 +107,6 @@ export const bn: LanguageTranslation = {
|
||||
copied: 'অনুলিপি সম্পন্ন!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'স্কিমা:',
|
||||
filter_by_schema: 'স্কিমা দ্বারা ফিল্টার করুন',
|
||||
search_schema: 'স্কিমা খুঁজুন...',
|
||||
no_schemas_found: 'কোনো স্কিমা পাওয়া যায়নি।',
|
||||
view_all_options: 'সমস্ত বিকল্প দেখুন...',
|
||||
tables_section: {
|
||||
tables: 'টেবিল',
|
||||
|
@@ -73,15 +73,6 @@ export const de: LanguageTranslation = {
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Kopieren fehlgeschlagen',
|
||||
@@ -117,10 +108,6 @@ export const de: LanguageTranslation = {
|
||||
copied: 'Kopiert!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tabellen',
|
||||
|
@@ -71,14 +71,6 @@ export const en = {
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Copy failed',
|
||||
@@ -113,10 +105,6 @@ export const en = {
|
||||
copied: 'Copied!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tables',
|
||||
|
@@ -106,10 +106,6 @@ export const es: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tablas',
|
||||
@@ -424,14 +420,6 @@ export const es: LanguageTranslation = {
|
||||
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
|
||||
export_diagram_dialog: {
|
||||
title: 'Export Diagram',
|
||||
|
@@ -105,10 +105,6 @@ export const fr: LanguageTranslation = {
|
||||
copied: 'Copié !',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tables',
|
||||
@@ -357,15 +353,6 @@ export const fr: LanguageTranslation = {
|
||||
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: {
|
||||
title: 'Sélectionner un Schéma',
|
||||
description:
|
||||
|
@@ -73,15 +73,6 @@ export const gu: LanguageTranslation = {
|
||||
cancel: 'રદ કરો',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'કઈંક વધારે સ્કીમા',
|
||||
description:
|
||||
'{{schemasCount}} સ્કીમા આ ડાયાગ્રામમાં છે. હાલમાં દર્શાવેલ છે: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'કઈ નહીં',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'નકલ નિષ્ફળ',
|
||||
@@ -116,10 +107,6 @@ export const gu: LanguageTranslation = {
|
||||
copied: 'નકલ થયું!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'સ્કીમા:',
|
||||
filter_by_schema: 'સ્કીમા દ્વારા ફિલ્ટર કરો',
|
||||
search_schema: 'સ્કીમા શોધો...',
|
||||
no_schemas_found: 'કોઈ સ્કીમા મળ્યા નથી.',
|
||||
view_all_options: 'બધા વિકલ્પો જુઓ...',
|
||||
tables_section: {
|
||||
tables: 'ટેબલ્સ',
|
||||
|
@@ -72,15 +72,6 @@ export const hi: LanguageTranslation = {
|
||||
cancel: 'रद्द करें',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'एकाधिक स्कीमा',
|
||||
description:
|
||||
'{{schemasCount}} स्कीमा इस आरेख में हैं। वर्तमान में प्रदर्शित: {{formattedSchemas}}।',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'कोई नहीं',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'कॉपी असफल',
|
||||
@@ -116,10 +107,6 @@ export const hi: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'स्कीमा:',
|
||||
filter_by_schema: 'स्कीमा द्वारा फ़िल्टर करें',
|
||||
search_schema: 'स्कीमा खोजें...',
|
||||
no_schemas_found: 'कोई स्कीमा नहीं मिला।',
|
||||
view_all_options: 'सभी विकल्प देखें...',
|
||||
tables_section: {
|
||||
tables: 'तालिकाएँ',
|
||||
|
@@ -71,14 +71,6 @@ export const hr: LanguageTranslation = {
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Kopiranje neuspješno',
|
||||
@@ -113,10 +105,6 @@ export const hr: LanguageTranslation = {
|
||||
copied: 'Kopirano!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tablice',
|
||||
|
@@ -72,15 +72,6 @@ export const id_ID: LanguageTranslation = {
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Gagal menyalin',
|
||||
@@ -115,10 +106,6 @@ export const id_ID: LanguageTranslation = {
|
||||
copied: 'Tersalin!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tabel',
|
||||
|
@@ -74,15 +74,6 @@ export const ja: LanguageTranslation = {
|
||||
cancel: 'キャンセル',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '複数のスキーマ',
|
||||
description:
|
||||
'このダイアグラムには{{schemasCount}}個のスキーマがあります。現在表示中: {{formattedSchemas}}。',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'なし',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'コピー失敗',
|
||||
@@ -119,10 +110,6 @@ export const ja: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'スキーマ:',
|
||||
filter_by_schema: 'スキーマでフィルタ',
|
||||
search_schema: 'スキーマを検索...',
|
||||
no_schemas_found: 'スキーマが見つかりません。',
|
||||
view_all_options: 'すべてのオプションを表示...',
|
||||
tables_section: {
|
||||
tables: 'テーブル',
|
||||
|
@@ -72,15 +72,6 @@ export const ko_KR: LanguageTranslation = {
|
||||
cancel: '취소',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '다중 스키마',
|
||||
description:
|
||||
'현재 다이어그램에 {{schemasCount}}개의 스키마가 있습니다. Currently displaying: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: '없음',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '복사 실패',
|
||||
@@ -115,10 +106,6 @@ export const ko_KR: LanguageTranslation = {
|
||||
copied: '복사됨!',
|
||||
|
||||
side_panel: {
|
||||
schema: '스키마:',
|
||||
filter_by_schema: '스키마로 필터링',
|
||||
search_schema: '스키마 검색...',
|
||||
no_schemas_found: '스키마를 찾을 수 없습니다.',
|
||||
view_all_options: '전체 옵션 보기...',
|
||||
tables_section: {
|
||||
tables: '테이블',
|
||||
|
@@ -73,15 +73,6 @@ export const mr: LanguageTranslation = {
|
||||
cancel: 'रद्द करा',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'एकाधिक स्कीमा',
|
||||
description:
|
||||
'{{schemasCount}} स्कीमा या आरेखात आहेत. सध्या दाखवत आहोत: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'काहीही नाही',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'कॉपी अयशस्वी',
|
||||
@@ -118,10 +109,6 @@ export const mr: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'स्कीमा:',
|
||||
filter_by_schema: 'स्कीमा द्वारे फिल्टर करा',
|
||||
search_schema: 'स्कीमा शोधा...',
|
||||
no_schemas_found: 'कोणतेही स्कीमा सापडले नाहीत.',
|
||||
view_all_options: 'सर्व पर्याय पहा...',
|
||||
tables_section: {
|
||||
tables: 'टेबल्स',
|
||||
|
@@ -73,15 +73,6 @@ export const ne: LanguageTranslation = {
|
||||
cancel: 'रद्द गर्नुहोस्',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'विविध स्कीमहरू',
|
||||
description:
|
||||
'{{schemasCount}} डायाग्राममा स्कीमहरू। हालको रूपमा देखाइएको छ: {{formattedSchemas}}।',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'कुनै पनि छैन',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'प्रतिलिपि असफल',
|
||||
@@ -116,10 +107,6 @@ export const ne: LanguageTranslation = {
|
||||
copied: 'प्रतिलिपि गरियो!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'स्कीम:',
|
||||
filter_by_schema: 'स्कीम अनुसार फिल्टर गर्नुहोस्',
|
||||
search_schema: 'स्कीम खोज्नुहोस्...',
|
||||
no_schemas_found: 'कुनै स्कीमहरू फेला परेनन्',
|
||||
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
|
||||
tables_section: {
|
||||
tables: 'तालिकाहरू',
|
||||
|
@@ -73,15 +73,6 @@ export const pt_BR: LanguageTranslation = {
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Falha na cópia',
|
||||
@@ -116,10 +107,6 @@ export const pt_BR: LanguageTranslation = {
|
||||
copied: 'Copiado!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tabelas',
|
||||
|
@@ -71,15 +71,6 @@ export const ru: LanguageTranslation = {
|
||||
cancel: 'Отменить',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'Множественные схемы',
|
||||
description:
|
||||
'{{schemasCount}} схем в этой диаграмме. В данный момент отображается: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'никто',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Ошибка копирования',
|
||||
@@ -113,10 +104,6 @@ export const ru: LanguageTranslation = {
|
||||
show_less: 'Показать меньше',
|
||||
|
||||
side_panel: {
|
||||
schema: 'Схема:',
|
||||
filter_by_schema: 'Фильтр по схеме',
|
||||
search_schema: 'Схема поиска...',
|
||||
no_schemas_found: 'Схемы не найдены.',
|
||||
view_all_options: 'Просмотреть все варианты...',
|
||||
tables_section: {
|
||||
tables: 'Таблицы',
|
||||
|
@@ -73,15 +73,6 @@ export const te: LanguageTranslation = {
|
||||
cancel: 'రద్దు',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'బహుళ స్కీమాలు',
|
||||
description:
|
||||
'{{schemasCount}} స్కీమాలు ఈ చిత్రంలో ఉన్నాయి. ప్రస్తుత స్కీమాలు: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'ఎదరికాదు',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'కాపీ విఫలమైంది',
|
||||
@@ -116,10 +107,6 @@ export const te: LanguageTranslation = {
|
||||
copied: 'కాపీ చేయబడింది!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'స్కీమా:',
|
||||
filter_by_schema: 'స్కీమా ద్వారా ఫిల్టర్ చేయండి',
|
||||
search_schema: 'స్కీమా కోసం శోధించండి...',
|
||||
no_schemas_found: 'ఏ స్కీమాలు కూడా కనుగొనబడలేదు.',
|
||||
view_all_options: 'అన్ని ఎంపికలను చూడండి...',
|
||||
tables_section: {
|
||||
tables: 'పట్టికలు',
|
||||
|
@@ -73,15 +73,6 @@ export const tr: LanguageTranslation = {
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Kopyalama başarısız',
|
||||
@@ -115,10 +106,6 @@ export const tr: LanguageTranslation = {
|
||||
copy_to_clipboard: 'Panoya Kopyala',
|
||||
copied: 'Kopyalandı!',
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tablolar',
|
||||
|
@@ -71,15 +71,6 @@ export const uk: LanguageTranslation = {
|
||||
cancel: 'Скасувати',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'Кілька схем',
|
||||
description:
|
||||
'{{schemasCount}} схеми на цій діаграмі. Зараз відображається: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'немає',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Помилка копіювання',
|
||||
@@ -114,10 +105,6 @@ export const uk: LanguageTranslation = {
|
||||
copied: 'Скопійовано!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'Схема:',
|
||||
filter_by_schema: 'Фільтрувати за схемою',
|
||||
search_schema: 'Пошук схеми…',
|
||||
no_schemas_found: 'Схеми не знайдено.',
|
||||
view_all_options: 'Переглянути всі параметри…',
|
||||
tables_section: {
|
||||
tables: 'Таблиці',
|
||||
|
@@ -72,15 +72,6 @@ export const vi: LanguageTranslation = {
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Sao chép thất bại',
|
||||
@@ -115,10 +106,6 @@ export const vi: LanguageTranslation = {
|
||||
copied: 'Đã sao chép!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Bảng',
|
||||
|
@@ -69,15 +69,6 @@ export const zh_CN: LanguageTranslation = {
|
||||
cancel: '取消',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '多个模式',
|
||||
description:
|
||||
'此关系图中有 {{schemasCount}} 个模式,当前显示:{{formattedSchemas}}。',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: '无',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '复制失败',
|
||||
@@ -112,10 +103,6 @@ export const zh_CN: LanguageTranslation = {
|
||||
copied: '复制了!',
|
||||
|
||||
side_panel: {
|
||||
schema: '模式:',
|
||||
filter_by_schema: '按模式筛选',
|
||||
search_schema: '搜索模式...',
|
||||
no_schemas_found: '未找到模式。',
|
||||
view_all_options: '查看所有选项...',
|
||||
tables_section: {
|
||||
tables: '表',
|
||||
|
@@ -69,15 +69,6 @@ export const zh_TW: LanguageTranslation = {
|
||||
cancel: '取消',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '多重 Schema',
|
||||
description:
|
||||
'此圖表中包含 {{schemasCount}} 個 Schema,目前顯示:{{formattedSchemas}}。',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: '無',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '複製失敗',
|
||||
@@ -112,10 +103,6 @@ export const zh_TW: LanguageTranslation = {
|
||||
copied: '已複製!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'Schema:',
|
||||
filter_by_schema: '依 Schema 篩選',
|
||||
search_schema: '搜尋 Schema...',
|
||||
no_schemas_found: '未找到 Schema。',
|
||||
view_all_options: '顯示所有選項...',
|
||||
tables_section: {
|
||||
tables: '表格',
|
||||
|
@@ -1,5 +1,4 @@
|
||||
export interface ChartDBConfig {
|
||||
defaultDiagramId: string;
|
||||
exportActions?: Date[];
|
||||
hiddenTablesByDiagram?: Record<string, string[]>; // Maps diagram ID to array of hidden table IDs
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import type { ViewInfo } from '../data/import-metadata/metadata-types/view-info';
|
||||
import { DatabaseType } from './database-type';
|
||||
import {
|
||||
schemaNameToDomainSchemaName,
|
||||
schemaNameToSchemaId,
|
||||
} from './db-schema';
|
||||
import { schemaNameToDomainSchemaName } from './db-schema';
|
||||
import { decodeViewDefinition, type DBTable } from './db-table';
|
||||
import { generateId } from '@/lib/utils';
|
||||
import type { AST } from 'node-sql-parser';
|
||||
@@ -27,18 +24,6 @@ export const dbDependencySchema: z.ZodType<DBDependency> = z.object({
|
||||
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> = {
|
||||
[DatabaseType.POSTGRESQL]: 'postgresql',
|
||||
[DatabaseType.MYSQL]: 'postgresql',
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import type { ForeignKeyInfo } from '../data/import-metadata/metadata-types/foreign-key-info';
|
||||
import type { DBField } from './db-field';
|
||||
import {
|
||||
schemaNameToDomainSchemaName,
|
||||
schemaNameToSchemaId,
|
||||
} from './db-schema';
|
||||
import { schemaNameToDomainSchemaName } from './db-schema';
|
||||
import type { DBTable } from './db-table';
|
||||
import { generateId } from '@/lib/utils';
|
||||
|
||||
@@ -43,20 +40,6 @@ export type RelationshipType =
|
||||
| 'many_to_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 = (
|
||||
field: DBField,
|
||||
isTablePKComplex: boolean
|
||||
|
@@ -18,10 +18,7 @@ import {
|
||||
deepCopy,
|
||||
generateId,
|
||||
} from '../utils';
|
||||
import {
|
||||
schemaNameToDomainSchemaName,
|
||||
schemaNameToSchemaId,
|
||||
} from './db-schema';
|
||||
import { schemaNameToDomainSchemaName } from './db-schema';
|
||||
import { DatabaseType } from './database-type';
|
||||
import type { DatabaseMetadata } from '../data/import-metadata/metadata-types/database-metadata';
|
||||
import { z } from 'zod';
|
||||
@@ -77,26 +74,6 @@ export const generateTableKey = ({
|
||||
tableName: string;
|
||||
}) => `${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 = (
|
||||
databaseType: DatabaseType,
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import { Table, Workflow, Group } from 'lucide-react';
|
||||
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||
|
||||
export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { createTable, filteredSchemas, schemas, readonly, createArea } =
|
||||
useChartDB();
|
||||
const { createTable, readonly, createArea } = useChartDB();
|
||||
const { schemasDisplayed } = useDiagramFilter();
|
||||
const { openCreateRelationshipDialog, openTableSchemaDialog } = useDialog();
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const { t } = useTranslation();
|
||||
@@ -30,7 +31,7 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||
y: event.clientY,
|
||||
});
|
||||
|
||||
if ((filteredSchemas?.length ?? 0) > 1) {
|
||||
if (schemasDisplayed.length > 1) {
|
||||
openTableSchemaDialog({
|
||||
onConfirm: ({ schema }) =>
|
||||
createTable({
|
||||
@@ -38,14 +39,12 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||
y: position.y,
|
||||
schema: schema.name,
|
||||
}),
|
||||
schemas: schemas.filter((schema) =>
|
||||
filteredSchemas?.includes(schema.id)
|
||||
),
|
||||
schemas: schemasDisplayed,
|
||||
});
|
||||
} else {
|
||||
const schema =
|
||||
filteredSchemas?.length === 1
|
||||
? schemas.find((s) => s.id === filteredSchemas[0])?.name
|
||||
schemasDisplayed?.length === 1
|
||||
? schemasDisplayed[0]?.name
|
||||
: undefined;
|
||||
createTable({
|
||||
x: position.x,
|
||||
@@ -58,8 +57,7 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||
createTable,
|
||||
screenToFlowPosition,
|
||||
openTableSchemaDialog,
|
||||
schemas,
|
||||
filteredSchemas,
|
||||
schemasDisplayed,
|
||||
]
|
||||
);
|
||||
|
||||
|
@@ -10,13 +10,14 @@ import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { Input } from '@/components/input/input';
|
||||
import { shouldShowTableSchemaBySchemaFilter } from '@/lib/domain/db-table';
|
||||
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
import { TreeView } from '@/components/tree-view/tree-view';
|
||||
import type { TreeNode } from '@/components/tree-view/tree';
|
||||
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 {
|
||||
onClose: () => void;
|
||||
@@ -24,10 +25,10 @@ export interface CanvasFilterProps {
|
||||
|
||||
type NodeType = 'schema' | 'table';
|
||||
|
||||
type SchemaContext = { name: string };
|
||||
type SchemaContext = { name: string; visible: boolean };
|
||||
type TableContext = {
|
||||
tableSchema?: string | null;
|
||||
hidden: boolean;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
type NodeContext = {
|
||||
@@ -43,15 +44,14 @@ type RelevantTableData = {
|
||||
|
||||
export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
const { t } = useTranslation();
|
||||
const { tables, databaseType } = useChartDB();
|
||||
const {
|
||||
tables,
|
||||
databaseType,
|
||||
hiddenTableIds,
|
||||
addHiddenTableId,
|
||||
removeHiddenTableId,
|
||||
filteredSchemas,
|
||||
filterSchemas,
|
||||
} = useChartDB();
|
||||
filter,
|
||||
toggleSchemaFilter,
|
||||
toggleTableFilter,
|
||||
clearTableIdsFilter,
|
||||
setTableIdsFilterEmpty,
|
||||
} = useDiagramFilter();
|
||||
const { fitView, setNodes } = useReactFlow();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
||||
@@ -69,14 +69,21 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
[tables]
|
||||
);
|
||||
|
||||
const databaseWithSchemas = useMemo(
|
||||
() => !!defaultSchemas[databaseType],
|
||||
[databaseType]
|
||||
);
|
||||
|
||||
// Convert tables to tree nodes
|
||||
const treeData = useMemo(() => {
|
||||
// Group tables by schema
|
||||
const tablesBySchema = new Map<string, RelevantTableData[]>();
|
||||
|
||||
relevantTableData.forEach((table) => {
|
||||
const schema =
|
||||
table.schema ?? defaultSchemas[databaseType] ?? 'default';
|
||||
const schema = !databaseWithSchemas
|
||||
? 'All Tables'
|
||||
: (table.schema ?? defaultSchemas[databaseType] ?? 'default');
|
||||
|
||||
if (!tablesBySchema.has(schema)) {
|
||||
tablesBySchema.set(schema, []);
|
||||
}
|
||||
@@ -92,28 +99,52 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
const nodes: TreeNode<NodeType, NodeContext>[] = [];
|
||||
|
||||
tablesBySchema.forEach((schemaTables, schemaName) => {
|
||||
const schemaId = schemaNameToSchemaId(schemaName);
|
||||
const schemaHidden = filteredSchemas
|
||||
? !filteredSchemas.includes(schemaId)
|
||||
: false;
|
||||
let schemaVisible;
|
||||
|
||||
if (databaseWithSchemas) {
|
||||
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> = {
|
||||
id: `schema-${schemaName}`,
|
||||
name: `${schemaName} (${schemaTables.length})`,
|
||||
type: 'schema',
|
||||
isFolder: true,
|
||||
icon: Database,
|
||||
context: { name: schemaName },
|
||||
className: schemaHidden ? 'opacity-50' : '',
|
||||
context: { name: schemaName, visible: schemaVisible },
|
||||
className: !schemaVisible ? 'opacity-50' : '',
|
||||
children: schemaTables.map(
|
||||
(table): TreeNode<NodeType, NodeContext> => {
|
||||
const tableHidden =
|
||||
hiddenTableIds?.includes(table.id) ?? false;
|
||||
const visibleBySchema =
|
||||
shouldShowTableSchemaBySchemaFilter({
|
||||
tableSchema: table.schema,
|
||||
filteredSchemas,
|
||||
});
|
||||
const hidden = tableHidden || !visibleBySchema;
|
||||
const tableVisible = filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
});
|
||||
|
||||
const hidden = !tableVisible;
|
||||
|
||||
return {
|
||||
id: table.id,
|
||||
@@ -123,7 +154,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
icon: Table,
|
||||
context: {
|
||||
tableSchema: table.schema,
|
||||
hidden: tableHidden,
|
||||
visible: tableVisible,
|
||||
},
|
||||
className: hidden ? 'opacity-50' : '',
|
||||
};
|
||||
@@ -134,7 +165,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
});
|
||||
|
||||
return nodes;
|
||||
}, [relevantTableData, databaseType, hiddenTableIds, filteredSchemas]);
|
||||
}, [relevantTableData, databaseType, filter, databaseWithSchemas]);
|
||||
|
||||
// Initialize expanded state with all schemas expanded
|
||||
useMemo(() => {
|
||||
@@ -170,17 +201,6 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
return result;
|
||||
}, [treeData, searchQuery]);
|
||||
|
||||
const toggleTableVisibility = useCallback(
|
||||
async (tableId: string, hidden: boolean) => {
|
||||
if (hidden) {
|
||||
await addHiddenTableId(tableId);
|
||||
} else {
|
||||
await removeHiddenTableId(tableId);
|
||||
}
|
||||
},
|
||||
[addHiddenTableId, removeHiddenTableId]
|
||||
);
|
||||
|
||||
const focusOnTable = useCallback(
|
||||
(tableId: string) => {
|
||||
// Make sure the table is visible
|
||||
@@ -222,9 +242,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
if (node.type === 'schema') {
|
||||
const schemaContext = node.context as SchemaContext;
|
||||
const schemaId = schemaNameToSchemaId(schemaContext.name);
|
||||
const schemaHidden = filteredSchemas
|
||||
? !filteredSchemas.includes(schemaId)
|
||||
: false;
|
||||
const schemaVisible = node.context.visible;
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -233,30 +251,20 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
className="size-7 h-fit p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// unhide all tables in this schema
|
||||
node.children?.forEach((child) => {
|
||||
if (
|
||||
child.type === 'table' &&
|
||||
hiddenTableIds?.includes(child.id)
|
||||
) {
|
||||
removeHiddenTableId(child.id);
|
||||
}
|
||||
});
|
||||
if (schemaHidden) {
|
||||
filterSchemas([
|
||||
...(filteredSchemas ?? []),
|
||||
schemaId,
|
||||
]);
|
||||
|
||||
if (databaseWithSchemas) {
|
||||
toggleSchemaFilter(schemaId);
|
||||
} else {
|
||||
filterSchemas(
|
||||
filteredSchemas?.filter(
|
||||
(s) => s !== schemaId
|
||||
) ?? []
|
||||
);
|
||||
// Toggle visibility of all tables in this schema
|
||||
if (node.context.visible) {
|
||||
setTableIdsFilterEmpty();
|
||||
} else {
|
||||
clearTableIdsFilter();
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{schemaHidden ? (
|
||||
{!schemaVisible ? (
|
||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="size-3.5" />
|
||||
@@ -268,13 +276,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
if (node.type === 'table') {
|
||||
const tableId = node.id;
|
||||
const tableContext = node.context as TableContext;
|
||||
const hidden = tableContext.hidden;
|
||||
const tableSchema = tableContext.tableSchema;
|
||||
|
||||
const visibleBySchema = shouldShowTableSchemaBySchemaFilter({
|
||||
tableSchema,
|
||||
filteredSchemas,
|
||||
});
|
||||
const tableVisible = tableContext.visible;
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -283,35 +285,10 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
className="size-7 h-fit p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!visibleBySchema && tableSchema) {
|
||||
// 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);
|
||||
}
|
||||
toggleTableFilter(tableId);
|
||||
}}
|
||||
>
|
||||
{hidden || !visibleBySchema ? (
|
||||
{!tableVisible ? (
|
||||
<EyeOff className="size-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="size-3.5" />
|
||||
@@ -323,13 +300,11 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
return null;
|
||||
},
|
||||
[
|
||||
toggleTableVisibility,
|
||||
filteredSchemas,
|
||||
filterSchemas,
|
||||
treeData,
|
||||
hiddenTableIds,
|
||||
addHiddenTableId,
|
||||
removeHiddenTableId,
|
||||
toggleSchemaFilter,
|
||||
toggleTableFilter,
|
||||
clearTableIdsFilter,
|
||||
setTableIdsFilterEmpty,
|
||||
databaseWithSchemas,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -338,19 +313,15 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
(node: TreeNode<NodeType, NodeContext>) => {
|
||||
if (node.type === 'table') {
|
||||
const tableContext = node.context as TableContext;
|
||||
const tableSchema = tableContext.tableSchema;
|
||||
const visibleBySchema = shouldShowTableSchemaBySchemaFilter({
|
||||
tableSchema,
|
||||
filteredSchemas,
|
||||
});
|
||||
const isTableVisible = tableContext.visible;
|
||||
|
||||
// Only focus if neither table is hidden nor filtered by schema
|
||||
if (!tableContext.hidden && visibleBySchema) {
|
||||
if (isTableVisible) {
|
||||
focusOnTable(node.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
[focusOnTable, filteredSchemas]
|
||||
[focusOnTable]
|
||||
);
|
||||
|
||||
// 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 { useTranslation } from 'react-i18next';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import {
|
||||
MIN_TABLE_SIZE,
|
||||
shouldShowTablesBySchemaFilter,
|
||||
} from '@/lib/domain/db-table';
|
||||
import { MIN_TABLE_SIZE } from '@/lib/domain/db-table';
|
||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -94,6 +91,10 @@ import { CanvasFilter } from './canvas-filter/canvas-filter';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { ShowAllButton } from './show-all-button';
|
||||
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 DEFAULT_EDGE_Z_INDEX = 0;
|
||||
@@ -118,13 +119,8 @@ const initialEdges: EdgeType[] = [];
|
||||
|
||||
const tableToTableNode = (
|
||||
table: DBTable,
|
||||
{
|
||||
filteredSchemas,
|
||||
hiddenTableIds,
|
||||
}: {
|
||||
filteredSchemas?: string[];
|
||||
hiddenTableIds?: string[];
|
||||
}
|
||||
filter: DiagramFilter | undefined,
|
||||
databaseType: DatabaseType
|
||||
): TableNodeType => {
|
||||
// Always use absolute position for now
|
||||
const position = { x: table.x, y: table.y };
|
||||
@@ -138,9 +134,11 @@ const tableToTableNode = (
|
||||
isOverlapping: false,
|
||||
},
|
||||
width: table.width ?? MIN_TABLE_SIZE,
|
||||
hidden:
|
||||
!shouldShowTablesBySchemaFilter(table, filteredSchemas) ||
|
||||
(hiddenTableIds?.includes(table.id) ?? false),
|
||||
hidden: !filterTable({
|
||||
table: { id: table.id, schema: table.schema },
|
||||
filter,
|
||||
options: { defaultSchema: defaultSchemas[databaseType] },
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -178,7 +176,6 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
removeDependencies,
|
||||
getField,
|
||||
databaseType,
|
||||
filteredSchemas,
|
||||
events,
|
||||
dependencies,
|
||||
readonly,
|
||||
@@ -186,7 +183,6 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
updateArea,
|
||||
highlightedCustomType,
|
||||
highlightCustomTypeId,
|
||||
hiddenTableIds,
|
||||
} = useChartDB();
|
||||
const { showSidePanel } = useLayout();
|
||||
const { effectiveTheme } = useTheme();
|
||||
@@ -204,12 +200,13 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
showFilter,
|
||||
setShowFilter,
|
||||
} = useCanvas();
|
||||
const { filter } = useDiagramFilter();
|
||||
|
||||
const [isInitialLoadingNodes, setIsInitialLoadingNodes] = useState(true);
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<NodeType>(
|
||||
initialTables.map((table) =>
|
||||
tableToTableNode(table, { filteredSchemas, hiddenTableIds })
|
||||
tableToTableNode(table, filter, databaseType)
|
||||
)
|
||||
);
|
||||
const [edges, setEdges, onEdgesChange] =
|
||||
@@ -223,12 +220,12 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
|
||||
useEffect(() => {
|
||||
const initialNodes = initialTables.map((table) =>
|
||||
tableToTableNode(table, { filteredSchemas, hiddenTableIds })
|
||||
tableToTableNode(table, filter, databaseType)
|
||||
);
|
||||
if (equal(initialNodes, nodes)) {
|
||||
setIsInitialLoadingNodes(false);
|
||||
}
|
||||
}, [initialTables, nodes, filteredSchemas, hiddenTableIds]);
|
||||
}, [initialTables, nodes, filter, databaseType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitialLoadingNodes) {
|
||||
@@ -391,10 +388,7 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
...tables.map((table) => {
|
||||
const isOverlapping =
|
||||
(overlapGraph.graph.get(table.id) ?? []).length > 0;
|
||||
const node = tableToTableNode(table, {
|
||||
filteredSchemas,
|
||||
hiddenTableIds,
|
||||
});
|
||||
const node = tableToTableNode(table, filter, databaseType);
|
||||
|
||||
// Check if table uses the highlighted custom type
|
||||
let hasHighlightedCustomType = false;
|
||||
@@ -429,21 +423,30 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
tables,
|
||||
areas,
|
||||
setNodes,
|
||||
filteredSchemas,
|
||||
hiddenTableIds,
|
||||
filter,
|
||||
databaseType,
|
||||
overlapGraph.lastUpdated,
|
||||
overlapGraph.graph,
|
||||
highlightOverlappingTables,
|
||||
highlightedCustomType,
|
||||
]);
|
||||
|
||||
const prevFilteredSchemas = useRef<string[] | undefined>(undefined);
|
||||
const prevFilter = useRef<DiagramFilter | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
if (!equal(filteredSchemas, prevFilteredSchemas.current)) {
|
||||
if (!equal(filter, prevFilter.current)) {
|
||||
debounce(() => {
|
||||
const overlappingTablesInDiagram = findOverlappingTables({
|
||||
tables: tables.filter((table) =>
|
||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
||||
filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
})
|
||||
),
|
||||
});
|
||||
setOverlapGraph(overlappingTablesInDiagram);
|
||||
@@ -453,9 +456,9 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
maxZoom: 0.8,
|
||||
});
|
||||
}, 500)();
|
||||
prevFilteredSchemas.current = filteredSchemas;
|
||||
prevFilter.current = filter;
|
||||
}
|
||||
}, [filteredSchemas, fitView, tables, setOverlapGraph]);
|
||||
}, [filter, fitView, tables, setOverlapGraph, databaseType]);
|
||||
|
||||
// Handle parent area updates when tables move
|
||||
const tablePositions = useMemo(
|
||||
@@ -1061,7 +1064,16 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
const diagramTables = event.data.diagram.tables ?? [];
|
||||
const overlappingTablesInDiagram = findOverlappingTables({
|
||||
tables: diagramTables.filter((table) =>
|
||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
||||
filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
})
|
||||
),
|
||||
});
|
||||
setOverlapGraph(overlappingTablesInDiagram);
|
||||
@@ -1072,8 +1084,9 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
setOverlapGraph,
|
||||
getNode,
|
||||
nodes,
|
||||
filteredSchemas,
|
||||
filter,
|
||||
setNodes,
|
||||
databaseType,
|
||||
]
|
||||
);
|
||||
|
||||
|
@@ -15,8 +15,8 @@ import { Button } from '@/components/button/button';
|
||||
import { keyboardShortcutsForOS } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
|
||||
import { KeyboardShortcutAction } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
|
||||
import { useCanvas } from '@/hooks/use-canvas';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||
|
||||
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 [zoom, setZoom] = useState<string>(convertToPercentage(getZoom()));
|
||||
const { setShowFilter } = useCanvas();
|
||||
const { hiddenTableIds } = useChartDB();
|
||||
const { hasActiveFilter } = useDiagramFilter();
|
||||
|
||||
const toggleFilter = useCallback(() => {
|
||||
setShowFilter((prev) => !prev);
|
||||
@@ -80,8 +80,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
|
||||
'transition-all duration-200',
|
||||
{
|
||||
'bg-pink-500 text-white hover:bg-pink-600 hover:text-white':
|
||||
(hiddenTableIds ?? []).length >
|
||||
0,
|
||||
hasActiveFilter,
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
@@ -1,14 +1,9 @@
|
||||
import React, { Suspense, useCallback, useEffect, useRef } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import React, { Suspense, useEffect } from 'react';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import { Toaster } from '@/components/toast/toaster';
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import { FullScreenLoaderProvider } from '@/context/full-screen-spinner-context/full-screen-spinner-provider';
|
||||
import { LayoutProvider } from '@/context/layout-context/layout-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 { DiffProvider } from '@/context/diff-context/diff-provider';
|
||||
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 SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
||||
@@ -43,21 +39,11 @@ export const EditorMobileLayoutLazy = React.lazy(
|
||||
);
|
||||
|
||||
const EditorPageComponent: React.FC = () => {
|
||||
const { diagramName, currentDiagram, schemas, filteredSchemas } =
|
||||
useChartDB();
|
||||
const { openSelectSchema, showSidePanel } = useLayout();
|
||||
const { diagramName, currentDiagram } = useChartDB();
|
||||
const { openStarUsDialog } = useDialog();
|
||||
const { diagramId } = useParams<{ diagramId: string }>();
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
const {
|
||||
hideMultiSchemaNotification,
|
||||
setHideMultiSchemaNotification,
|
||||
starUsDialogLastOpen,
|
||||
setStarUsDialogLastOpen,
|
||||
githubRepoOpened,
|
||||
} = useLocalConfig();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { starUsDialogLastOpen, setStarUsDialogLastOpen, githubRepoOpened } =
|
||||
useLocalConfig();
|
||||
const { initialDiagram } = useDiagramLoader();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -85,73 +71,6 @@ const EditorPageComponent: React.FC = () => {
|
||||
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 (
|
||||
<>
|
||||
<Helmet>
|
||||
@@ -202,21 +121,23 @@ export const EditorPage: React.FC = () => (
|
||||
<RedoUndoStackProvider>
|
||||
<DiffProvider>
|
||||
<ChartDBProvider>
|
||||
<HistoryProvider>
|
||||
<ReactFlowProvider>
|
||||
<CanvasProvider>
|
||||
<ExportImageProvider>
|
||||
<AlertProvider>
|
||||
<DialogProvider>
|
||||
<KeyboardShortcutsProvider>
|
||||
<EditorPageComponent />
|
||||
</KeyboardShortcutsProvider>
|
||||
</DialogProvider>
|
||||
</AlertProvider>
|
||||
</ExportImageProvider>
|
||||
</CanvasProvider>
|
||||
</ReactFlowProvider>
|
||||
</HistoryProvider>
|
||||
<DiagramFilterProvider>
|
||||
<HistoryProvider>
|
||||
<ReactFlowProvider>
|
||||
<CanvasProvider>
|
||||
<ExportImageProvider>
|
||||
<AlertProvider>
|
||||
<DialogProvider>
|
||||
<KeyboardShortcutsProvider>
|
||||
<EditorPageComponent />
|
||||
</KeyboardShortcutsProvider>
|
||||
</DialogProvider>
|
||||
</AlertProvider>
|
||||
</ExportImageProvider>
|
||||
</CanvasProvider>
|
||||
</ReactFlowProvider>
|
||||
</HistoryProvider>
|
||||
</DiagramFilterProvider>
|
||||
</ChartDBProvider>
|
||||
</DiffProvider>
|
||||
</RedoUndoStackProvider>
|
||||
|
@@ -34,6 +34,7 @@ import {
|
||||
} from '@/lib/domain/db-custom-type';
|
||||
import { Badge } from '@/components/badge/badge';
|
||||
import { checkIfCustomTypeUsed } from '../utils';
|
||||
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||
|
||||
export interface CustomTypeListItemHeaderProps {
|
||||
customType: DBCustomType;
|
||||
@@ -45,12 +46,11 @@ export const CustomTypeListItemHeader: React.FC<
|
||||
const {
|
||||
updateCustomType,
|
||||
removeCustomType,
|
||||
schemas,
|
||||
filteredSchemas,
|
||||
highlightedCustomType,
|
||||
highlightCustomTypeId,
|
||||
tables,
|
||||
} = useChartDB();
|
||||
const { schemasDisplayed } = useDiagramFilter();
|
||||
const { t } = useTranslation();
|
||||
const [editMode, setEditMode] = React.useState(false);
|
||||
const [customTypeName, setCustomTypeName] = React.useState(customType.name);
|
||||
@@ -161,11 +161,11 @@ export const CustomTypeListItemHeader: React.FC<
|
||||
isHighlighted,
|
||||
]);
|
||||
|
||||
let schemaToDisplay;
|
||||
|
||||
if (schemas.length > 1 && !!filteredSchemas && filteredSchemas.length > 1) {
|
||||
schemaToDisplay = customType.schema;
|
||||
}
|
||||
const schemaToDisplay = useMemo(() => {
|
||||
if (schemasDisplayed.length > 1) {
|
||||
return customType.schema;
|
||||
}
|
||||
}, [customType.schema, schemasDisplayed.length]);
|
||||
|
||||
return (
|
||||
<div className="group flex h-11 flex-1 items-center justify-between gap-1 overflow-hidden">
|
||||
|
@@ -13,13 +13,16 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||
import { shouldShowDependencyBySchemaFilter } from '@/lib/domain/db-dependency';
|
||||
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 const DependenciesSection: React.FC<DependenciesSectionProps> = () => {
|
||||
const { dependencies, filteredSchemas, getTable } = useChartDB();
|
||||
const { dependencies, getTable, databaseType } = useChartDB();
|
||||
const { filter } = useDiagramFilter();
|
||||
const [filterText, setFilterText] = React.useState('');
|
||||
const { closeAllDependenciesInSidebar } = useLayout();
|
||||
const { t } = useTranslation();
|
||||
@@ -44,12 +47,26 @@ export const DependenciesSection: React.FC<DependenciesSectionProps> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const filterSchema: (dependency: DBDependency) => boolean = (
|
||||
const filterDependencies: (dependency: DBDependency) => boolean = (
|
||||
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
|
||||
.filter(filterSchema)
|
||||
.filter(filterDependencies)
|
||||
.filter(filterName)
|
||||
.sort((a, b) => {
|
||||
const dependentTableA = getTable(a.dependentTableId);
|
||||
@@ -60,7 +77,7 @@ export const DependenciesSection: React.FC<DependenciesSectionProps> = () => {
|
||||
`${dependentTableB?.name}${tableB?.name}`
|
||||
);
|
||||
});
|
||||
}, [dependencies, filterText, filteredSchemas, getTable]);
|
||||
}, [dependencies, filterText, filter, getTable, databaseType]);
|
||||
|
||||
return (
|
||||
<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 { useChartDB } from '@/hooks/use-chartdb';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
import { shouldShowRelationshipBySchemaFilter } from '@/lib/domain/db-relationship';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import { EmptyState } from '@/components/empty-state/empty-state';
|
||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
||||
@@ -16,11 +15,15 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
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 const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
||||
const { relationships, filteredSchemas } = useChartDB();
|
||||
const { relationships, databaseType } = useChartDB();
|
||||
const { filter } = useDiagramFilter();
|
||||
const [filterText, setFilterText] = React.useState('');
|
||||
const { closeAllRelationshipsInSidebar } = useLayout();
|
||||
const { t } = useTranslation();
|
||||
@@ -34,13 +37,26 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
||||
!filterText?.trim?.() ||
|
||||
relationship.name.toLowerCase().includes(filterText.toLowerCase());
|
||||
|
||||
const filterSchema: (relationship: DBRelationship) => boolean = (
|
||||
const filterRelationships: (relationship: DBRelationship) => boolean = (
|
||||
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);
|
||||
}, [relationships, filterText, filteredSchemas]);
|
||||
return relationships.filter(filterRelationships).filter(filterName);
|
||||
}, [relationships, filterText, filter, databaseType]);
|
||||
|
||||
const handleCreateRelationship = useCallback(async () => {
|
||||
setFilterText('');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -12,8 +12,6 @@ import { RelationshipsSection } from './relationships-section/relationships-sect
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import type { SidebarSection } from '@/context/layout-context/layout-context';
|
||||
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 { DependenciesSection } from './dependencies-section/dependencies-section';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
@@ -25,69 +23,12 @@ export interface SidePanelProps {}
|
||||
|
||||
export const SidePanel: React.FC<SidePanelProps> = () => {
|
||||
const { t } = useTranslation();
|
||||
const { schemas, filterSchemas, filteredSchemas, databaseType } =
|
||||
useChartDB();
|
||||
const {
|
||||
selectSidebarSection,
|
||||
selectedSidebarSection,
|
||||
isSelectSchemaOpen,
|
||||
openSelectSchema,
|
||||
closeSelectSchema,
|
||||
} = useLayout();
|
||||
const { databaseType } = useChartDB();
|
||||
const { selectSidebarSection, selectedSidebarSection } = useLayout();
|
||||
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 (
|
||||
<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 ? (
|
||||
<div className="flex justify-center border-b pt-0.5">
|
||||
<Select
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
CircleDotDashed,
|
||||
GripVertical,
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
import { cloneTable } from '@/lib/clone';
|
||||
import type { DBSchema } from '@/lib/domain';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||
|
||||
export interface TableListItemHeaderProps {
|
||||
table: DBTable;
|
||||
@@ -55,9 +56,9 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
||||
createField,
|
||||
createTable,
|
||||
schemas,
|
||||
filteredSchemas,
|
||||
databaseType,
|
||||
} = useChartDB();
|
||||
const { schemasDisplayed } = useDiagramFilter();
|
||||
const { openTableSchemaDialog } = useDialog();
|
||||
const { t } = useTranslation();
|
||||
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) {
|
||||
schemaToDisplay = table.schema;
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (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 { Input } from '@/components/input/input';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
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 { getOperatingSystem } from '@/lib/utils';
|
||||
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 const TablesSection: React.FC<TablesSectionProps> = () => {
|
||||
const { createTable, tables, filteredSchemas, schemas } = useChartDB();
|
||||
const { createTable, tables, databaseType } = useChartDB();
|
||||
const { filter, schemasDisplayed } = useDiagramFilter();
|
||||
const { openTableSchemaDialog } = useDialog();
|
||||
const viewport = useViewport();
|
||||
const { t } = useTranslation();
|
||||
@@ -39,11 +42,20 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
|
||||
!filterText?.trim?.() ||
|
||||
table.name.toLowerCase().includes(filterText.toLowerCase());
|
||||
|
||||
const filterSchema: (table: DBTable) => boolean = (table) =>
|
||||
shouldShowTablesBySchemaFilter(table, filteredSchemas);
|
||||
const filterTables: (table: DBTable) => boolean = (table) =>
|
||||
filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
});
|
||||
|
||||
return tables.filter(filterSchema).filter(filterTableName);
|
||||
}, [tables, filterText, filteredSchemas]);
|
||||
return tables.filter(filterTables).filter(filterTableName);
|
||||
}, [tables, filterText, filter, databaseType]);
|
||||
|
||||
const createTableWithLocation = useCallback(
|
||||
async ({ schema }: { schema?: DBSchema }) => {
|
||||
@@ -71,25 +83,20 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
|
||||
const handleCreateTable = useCallback(async () => {
|
||||
setFilterText('');
|
||||
|
||||
if ((filteredSchemas?.length ?? 0) > 1) {
|
||||
if (schemasDisplayed.length > 1) {
|
||||
openTableSchemaDialog({
|
||||
onConfirm: createTableWithLocation,
|
||||
schemas: schemas.filter((schema) =>
|
||||
filteredSchemas?.includes(schema.id)
|
||||
),
|
||||
schemas: schemasDisplayed,
|
||||
});
|
||||
} else {
|
||||
const schema =
|
||||
filteredSchemas?.length === 1
|
||||
? schemas.find((s) => s.id === filteredSchemas[0])
|
||||
: undefined;
|
||||
schemasDisplayed.length === 1 ? schemasDisplayed[0] : undefined;
|
||||
createTableWithLocation({ schema });
|
||||
}
|
||||
}, [
|
||||
createTableWithLocation,
|
||||
filteredSchemas,
|
||||
schemasDisplayed,
|
||||
openTableSchemaDialog,
|
||||
schemas,
|
||||
setFilterText,
|
||||
]);
|
||||
|
||||
|
Reference in New Issue
Block a user