From 4f1d3295c09782ab46d82ce21b662032aa094f22 Mon Sep 17 00:00:00 2001 From: Guy Ben-Aharon Date: Thu, 7 Aug 2025 14:55:35 +0300 Subject: [PATCH] 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 --- index.html | 1 + .../canvas-context/canvas-provider.tsx | 33 +- .../chartdb-context/chartdb-context.tsx | 15 - .../chartdb-context/chartdb-provider.tsx | 82 +--- src/context/config-context/config-context.tsx | 14 - .../config-context/config-provider.tsx | 77 +-- .../diagram-filter-context.tsx | 48 ++ .../diagram-filter-provider.tsx | 442 ++++++++++++++++++ .../use-diagram-filter.ts | 4 + src/context/layout-context/layout-context.tsx | 8 - .../layout-context/layout-provider.tsx | 10 - .../local-config-context.tsx | 16 - .../local-config-provider.tsx | 31 +- .../storage-context/storage-context.tsx | 13 + .../storage-context/storage-provider.tsx | 55 +++ .../export-sql-dialog/export-sql-dialog.tsx | 62 ++- .../table-schema-dialog.tsx | 12 +- src/i18n/locales/ar.ts | 13 - src/i18n/locales/bn.ts | 13 - src/i18n/locales/de.ts | 13 - src/i18n/locales/en.ts | 12 - src/i18n/locales/es.ts | 12 - src/i18n/locales/fr.ts | 13 - src/i18n/locales/gu.ts | 13 - src/i18n/locales/hi.ts | 13 - src/i18n/locales/hr.ts | 12 - src/i18n/locales/id_ID.ts | 13 - src/i18n/locales/ja.ts | 13 - src/i18n/locales/ko_KR.ts | 13 - src/i18n/locales/mr.ts | 13 - src/i18n/locales/ne.ts | 13 - src/i18n/locales/pt_BR.ts | 13 - src/i18n/locales/ru.ts | 13 - src/i18n/locales/te.ts | 13 - src/i18n/locales/tr.ts | 13 - src/i18n/locales/uk.ts | 13 - src/i18n/locales/vi.ts | 13 - src/i18n/locales/zh_CN.ts | 13 - src/i18n/locales/zh_TW.ts | 13 - src/lib/domain/config.ts | 1 - src/lib/domain/db-dependency.ts | 17 +- src/lib/domain/db-relationship.ts | 19 +- src/lib/domain/db-table.ts | 25 +- .../domain/diagram-filter/diagram-filter.ts | 147 ++++++ src/lib/domain/diagram-filter/filter.ts | 114 +++++ .../canvas/canvas-context-menu.tsx | 18 +- .../canvas/canvas-filter/canvas-filter.tsx | 193 ++++---- src/pages/editor-page/canvas/canvas.tsx | 77 +-- .../editor-page/canvas/toolbar/toolbar.tsx | 7 +- src/pages/editor-page/editor-page.tsx | 123 +---- .../custom-type-list-item-header.tsx | 14 +- .../dependencies-section.tsx | 29 +- .../relationships-section.tsx | 28 +- .../editor-page/side-panel/side-panel.tsx | 65 +-- .../table-list-item-header.tsx | 17 +- .../tables-section/tables-section.tsx | 37 +- 56 files changed, 1161 insertions(+), 976 deletions(-) create mode 100644 src/context/diagram-filter-context/diagram-filter-context.tsx create mode 100644 src/context/diagram-filter-context/diagram-filter-provider.tsx create mode 100644 src/context/diagram-filter-context/use-diagram-filter.ts create mode 100644 src/lib/domain/diagram-filter/diagram-filter.ts create mode 100644 src/lib/domain/diagram-filter/filter.ts diff --git a/index.html b/index.html index 4c30b83b..8af100c7 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,7 @@ ChartDB - Create & Visualize Database Schema Diagrams + { - const { tables, relationships, updateTablesState, filteredSchemas } = + const { tables, relationships, updateTablesState, databaseType } = useChartDB(); + const { filter } = useDiagramFilter(); const { fitView } = useReactFlow(); const [overlapGraph, setOverlapGraph] = useState>(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 ( diff --git a/src/context/chartdb-context/chartdb-context.tsx b/src/context/chartdb-context/chartdb-context.tsx index 19c165d6..31cc68b1 100644 --- a/src/context/chartdb-context/chartdb-context.tsx +++ b/src/context/chartdb-context/chartdb-context.tsx @@ -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; updateDiagramName: ( @@ -284,11 +281,6 @@ export interface ChartDBContext { customType: Partial, options?: { updateHistory: boolean } ) => Promise; - - // Filters - hiddenTableIds?: string[]; - addHiddenTableId: (tableId: string) => Promise; - removeHiddenTableId: (tableId: string) => Promise; } export const chartDBContext = createContext({ @@ -302,8 +294,6 @@ export const chartDBContext = createContext({ customTypes: [], schemas: [], highlightCustomTypeId: emptyFn, - filteredSchemas: [], - filterSchemas: emptyFn, currentDiagram: { id: '', name: '', @@ -386,9 +376,4 @@ export const chartDBContext = createContext({ removeCustomType: emptyFn, removeCustomTypes: emptyFn, updateCustomType: emptyFn, - - // Filters - hiddenTableIds: [], - addHiddenTableId: emptyFn, - removeHiddenTableId: emptyFn, }); diff --git a/src/context/chartdb-context/chartdb-provider.tsx b/src/context/chartdb-context/chartdb-provider.tsx index b1dcdea6..5243377a 100644 --- a/src/context/chartdb-context/chartdb-provider.tsx +++ b/src/context/chartdb-context/chartdb-provider.tsx @@ -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(); - 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(new Date()); @@ -72,7 +65,7 @@ export const ChartDBProvider: React.FC< const [customTypes, setCustomTypes] = useState( diagram?.customTypes ?? [] ); - const [hiddenTableIds, setHiddenTableIds] = useState([]); + 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 ( ; updateFn?: (config: ChartDBConfig) => ChartDBConfig; }) => Promise; - getHiddenTablesForDiagram: (diagramId: string) => string[]; - setHiddenTablesForDiagram: ( - diagramId: string, - hiddenTableIds: string[] - ) => Promise; - hideTableForDiagram: (diagramId: string, tableId: string) => Promise; - unhideTableForDiagram: ( - diagramId: string, - tableId: string - ) => Promise; } export const ConfigContext = createContext({ config: undefined, updateConfig: emptyFn, - getHiddenTablesForDiagram: () => [], - setHiddenTablesForDiagram: emptyFn, - hideTableForDiagram: emptyFn, - unhideTableForDiagram: emptyFn, }); diff --git a/src/context/config-context/config-provider.tsx b/src/context/config-context/config-provider.tsx index f85ad8ae..5a9e26cd 100644 --- a/src/context/config-context/config-provider.tsx +++ b/src/context/config-context/config-provider.tsx @@ -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 = ({ children, }) => { const { getConfig, updateConfig: updateDataConfig } = useStorage(); - const [config, setConfig] = React.useState(); + const [config, setConfig] = useState(); useEffect(() => { const loadConfig = async () => { @@ -44,84 +44,11 @@ export const ConfigProvider: React.FC = ({ return promise; }; - const getHiddenTablesForDiagram = (diagramId: string): string[] => { - return config?.hiddenTablesByDiagram?.[diagramId] ?? []; - }; - - const setHiddenTablesForDiagram = async ( - diagramId: string, - hiddenTableIds: string[] - ): Promise => { - return updateConfig({ - updateFn: (currentConfig) => ({ - ...currentConfig, - hiddenTablesByDiagram: { - ...currentConfig.hiddenTablesByDiagram, - [diagramId]: hiddenTableIds, - }, - }), - }); - }; - - const hideTableForDiagram = async ( - diagramId: string, - tableId: string - ): Promise => { - 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 => { - 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 ( {children} diff --git a/src/context/diagram-filter-context/diagram-filter-context.tsx b/src/context/diagram-filter-context/diagram-filter-context.tsx new file mode 100644 index 00000000..1f716dd7 --- /dev/null +++ b/src/context/diagram-filter-context/diagram-filter-context.tsx @@ -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({ + 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: [], +}); diff --git a/src/context/diagram-filter-context/diagram-filter-provider.tsx b/src/context/diagram-filter-context/diagram-filter-provider.tsx new file mode 100644 index 00000000..2ab14340 --- /dev/null +++ b/src/context/diagram-filter-context/diagram-filter-provider.tsx @@ -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 = ({ + children, +}) => { + const { diagramId, tables, schemas, databaseType } = useChartDB(); + const { getDiagramFilter, updateDiagramFilter } = useStorage(); + const [filter, setFilter] = useState({}); + + 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(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 ( + + {children} + + ); +}; diff --git a/src/context/diagram-filter-context/use-diagram-filter.ts b/src/context/diagram-filter-context/use-diagram-filter.ts new file mode 100644 index 00000000..a0ee1b38 --- /dev/null +++ b/src/context/diagram-filter-context/use-diagram-filter.ts @@ -0,0 +1,4 @@ +import { useContext } from 'react'; +import { diagramFilterContext } from './diagram-filter-context'; + +export const useDiagramFilter = () => useContext(diagramFilterContext); diff --git a/src/context/layout-context/layout-context.tsx b/src/context/layout-context/layout-context.tsx index 12885e5a..a3c2df1a 100644 --- a/src/context/layout-context/layout-context.tsx +++ b/src/context/layout-context/layout-context.tsx @@ -36,10 +36,6 @@ export interface LayoutContext { hideSidePanel: () => void; showSidePanel: () => void; toggleSidePanel: () => void; - - isSelectSchemaOpen: boolean; - openSelectSchema: () => void; - closeSelectSchema: () => void; } export const layoutContext = createContext({ @@ -70,8 +66,4 @@ export const layoutContext = createContext({ hideSidePanel: emptyFn, showSidePanel: emptyFn, toggleSidePanel: emptyFn, - - isSelectSchemaOpen: false, - openSelectSchema: emptyFn, - closeSelectSchema: emptyFn, }); diff --git a/src/context/layout-context/layout-provider.tsx b/src/context/layout-context/layout-provider.tsx index d1db4cb3..c7aec87e 100644 --- a/src/context/layout-context/layout-provider.tsx +++ b/src/context/layout-context/layout-provider.tsx @@ -23,8 +23,6 @@ export const LayoutProvider: React.FC = ({ React.useState('tables'); const [isSidePanelShowed, setIsSidePanelShowed] = React.useState(isDesktop); - const [isSelectSchemaOpen, setIsSelectSchemaOpen] = - React.useState(false); const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] = () => setOpenedTableInSidebar(''); @@ -88,11 +86,6 @@ export const LayoutProvider: React.FC = ({ setOpenedTableInSidebar(customTypeId); }; - const openSelectSchema: LayoutContext['openSelectSchema'] = () => - setIsSelectSchemaOpen(true); - - const closeSelectSchema: LayoutContext['closeSelectSchema'] = () => - setIsSelectSchemaOpen(false); return ( = ({ hideSidePanel, showSidePanel, toggleSidePanel, - isSelectSchemaOpen, - openSelectSchema, - closeSelectSchema, openedDependencyInSidebar, openDependencyFromSidebar, closeAllDependenciesInSidebar, diff --git a/src/context/local-config-context/local-config-context.tsx b/src/context/local-config-context/local-config-context.tsx index c0f2fb0d..d3771dae 100644 --- a/src/context/local-config-context/local-config-context.tsx +++ b/src/context/local-config-context/local-config-context.tsx @@ -4,8 +4,6 @@ import type { Theme } from '../theme-context/theme-context'; export type ScrollAction = 'pan' | 'zoom'; -export type SchemasFilter = Record; - 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>; - 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({ scrollAction: 'pan', setScrollAction: emptyFn, - schemasFilter: {}, - setSchemasFilter: emptyFn, - showCardinality: true, setShowCardinality: emptyFn, showFieldAttributes: true, setShowFieldAttributes: emptyFn, - hideMultiSchemaNotification: false, - setHideMultiSchemaNotification: emptyFn, - githubRepoOpened: false, setGithubRepoOpened: emptyFn, diff --git a/src/context/local-config-context/local-config-provider.tsx b/src/context/local-config-context/local-config-provider.tsx index b582e03a..c2b360fe 100644 --- a/src/context/local-config-context/local-config-provider.tsx +++ b/src/context/local-config-context/local-config-provider.tsx @@ -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 = ({ (localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan' ); - const [schemasFilter, setSchemasFilter] = React.useState( - JSON.parse( - localStorage.getItem(schemasFilterKey) || '{}' - ) as SchemasFilter - ); - const [showCardinality, setShowCardinality] = React.useState( (localStorage.getItem(showCardinalityKey) || 'true') === 'true' ); @@ -40,12 +32,6 @@ export const LocalConfigProvider: React.FC = ({ (localStorage.getItem(showFieldAttributesKey) || 'true') === 'true' ); - const [hideMultiSchemaNotification, setHideMultiSchemaNotification] = - React.useState( - (localStorage.getItem(hideMultiSchemaNotificationKey) || - 'false') === 'true' - ); - const [githubRepoOpened, setGithubRepoOpened] = React.useState( (localStorage.getItem(githubRepoOpenedKey) || 'false') === 'true' ); @@ -77,13 +63,6 @@ export const LocalConfigProvider: React.FC = ({ 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 = ({ 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 = ({ setTheme, scrollAction, setScrollAction, - schemasFilter, - setSchemasFilter, showCardinality, setShowCardinality, showFieldAttributes, setShowFieldAttributes, - hideMultiSchemaNotification, - setHideMultiSchemaNotification, setGithubRepoOpened, githubRepoOpened, starUsDialogLastOpen, diff --git a/src/context/storage-context/storage-context.tsx b/src/context/storage-context/storage-context.tsx index cfa697dd..565f4997 100644 --- a/src/context/storage-context/storage-context.tsx +++ b/src/context/storage-context/storage-context.tsx @@ -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; updateConfig: (config: Partial) => Promise; + // Diagram filter operations + getDiagramFilter: (diagramId: string) => Promise; + updateDiagramFilter: ( + diagramId: string, + filter: DiagramFilter + ) => Promise; + deleteDiagramFilter: (diagramId: string) => Promise; + // Diagram operations addDiagram: (params: { diagram: Diagram }) => Promise; 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, diff --git a/src/context/storage-context/storage-provider.tsx b/src/context/storage-context/storage-provider.tsx index f72897a0..709df61a 100644 --- a/src/context/storage-context/storage-provider.tsx +++ b/src/context/storage-context/storage-provider.tsx @@ -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 = ({ children, @@ -44,6 +45,10 @@ export const StorageProvider: React.FC = ({ 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 = ({ 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 = ({ [db] ); + const getDiagramFilter: StorageContext['getDiagramFilter'] = useCallback( + async (diagramId: string): Promise => { + return await db.diagram_filters.get({ diagramId }); + }, + [db] + ); + + const updateDiagramFilter: StorageContext['updateDiagramFilter'] = + useCallback( + async (diagramId, filter): Promise => { + await db.diagram_filters.put({ + diagramId, + ...filter, + }); + }, + [db] + ); + + const deleteDiagramFilter: StorageContext['deleteDiagramFilter'] = + useCallback( + async (diagramId: string): Promise => { + 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 = ({ deleteCustomType, listCustomTypes, deleteDiagramCustomTypes, + getDiagramFilter, + updateDiagramFilter, + deleteDiagramFilter, }} > {children} diff --git a/src/dialogs/export-sql-dialog/export-sql-dialog.tsx b/src/dialogs/export-sql-dialog/export-sql-dialog.tsx index 2848604f..949d3a53 100644 --- a/src/dialogs/export-sql-dialog/export-sql-dialog.tsx +++ b/src/dialogs/export-sql-dialog/export-sql-dialog.tsx @@ -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 = ({ targetDatabaseType, }) => { const { closeExportSQLDialog } = useDialog(); - const { currentDiagram, filteredSchemas } = useChartDB(); + const { currentDiagram } = useChartDB(); + const { filter } = useDiagramFilter(); const { t } = useTranslation(); const [script, setScript] = React.useState(); const [error, setError] = React.useState(false); @@ -48,7 +55,16 @@ export const ExportSQLDialog: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ signal: abortControllerRef.current?.signal, }); } - }, [targetDatabaseType, currentDiagram, filteredSchemas]); + }, [targetDatabaseType, currentDiagram, filter]); useEffect(() => { if (!dialog.open) { diff --git a/src/dialogs/table-schema-dialog/table-schema-dialog.tsx b/src/dialogs/table-schema-dialog/table-schema-dialog.tsx index 285dfa70..d16b3d32 100644 --- a/src/dialogs/table-schema-dialog/table-schema-dialog.tsx +++ b/src/dialogs/table-schema-dialog/table-schema-dialog.tsx @@ -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 = ({ allowSchemaCreation = false, }) => { const { t } = useTranslation(); - const { databaseType, filteredSchemas, filterSchemas } = useChartDB(); + const { databaseType } = useChartDB(); + const { addSchemaIfFiltered } = useDiagramFilter(); const [selectedSchemaId, setSelectedSchemaId] = useState( table?.schema ? schemaNameToSchemaId(table.schema) @@ -112,18 +114,14 @@ export const TableSchemaDialog: React.FC = ({ onConfirm({ schema }); } - filterSchemas([ - ...(filteredSchemas ?? schemas.map((s) => s.id)), - createdSchemaId, - ]); + addSchemaIfFiltered(createdSchemaId); }, [ onConfirm, selectedSchemaId, schemas, isCreatingNew, newSchemaName, - filteredSchemas, - filterSchemas, + addSchemaIfFiltered, ]); const schemaOptions: SelectBoxOption[] = useMemo( diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 03511107..743fbe81 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -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: 'الجداول', diff --git a/src/i18n/locales/bn.ts b/src/i18n/locales/bn.ts index e3a0745c..666e11e9 100644 --- a/src/i18n/locales/bn.ts +++ b/src/i18n/locales/bn.ts @@ -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: 'টেবিল', diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index d920f36b..f459e555 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -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', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index cafdc417..02233933 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -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', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 71dc0427..14898cf5 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -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', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index fc2d79bd..18918d17 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -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: diff --git a/src/i18n/locales/gu.ts b/src/i18n/locales/gu.ts index 8ffb8fe3..2ce080ec 100644 --- a/src/i18n/locales/gu.ts +++ b/src/i18n/locales/gu.ts @@ -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: 'ટેબલ્સ', diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 3b661b66..f7880182 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -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: 'तालिकाएँ', diff --git a/src/i18n/locales/hr.ts b/src/i18n/locales/hr.ts index 371618d9..03cbee90 100644 --- a/src/i18n/locales/hr.ts +++ b/src/i18n/locales/hr.ts @@ -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', diff --git a/src/i18n/locales/id_ID.ts b/src/i18n/locales/id_ID.ts index 7eee0558..b54c1944 100644 --- a/src/i18n/locales/id_ID.ts +++ b/src/i18n/locales/id_ID.ts @@ -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', diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index d705e54f..d7e62e31 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -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: 'テーブル', diff --git a/src/i18n/locales/ko_KR.ts b/src/i18n/locales/ko_KR.ts index c7090b2f..3d3e1dae 100644 --- a/src/i18n/locales/ko_KR.ts +++ b/src/i18n/locales/ko_KR.ts @@ -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: '테이블', diff --git a/src/i18n/locales/mr.ts b/src/i18n/locales/mr.ts index 77920c65..699cd666 100644 --- a/src/i18n/locales/mr.ts +++ b/src/i18n/locales/mr.ts @@ -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: 'टेबल्स', diff --git a/src/i18n/locales/ne.ts b/src/i18n/locales/ne.ts index f07d05cf..c2f7da22 100644 --- a/src/i18n/locales/ne.ts +++ b/src/i18n/locales/ne.ts @@ -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: 'तालिकाहरू', diff --git a/src/i18n/locales/pt_BR.ts b/src/i18n/locales/pt_BR.ts index c74d1f31..3b96c34d 100644 --- a/src/i18n/locales/pt_BR.ts +++ b/src/i18n/locales/pt_BR.ts @@ -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', diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index a8bacc32..75a1b0cb 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -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: 'Таблицы', diff --git a/src/i18n/locales/te.ts b/src/i18n/locales/te.ts index 1d3fa1ac..de434249 100644 --- a/src/i18n/locales/te.ts +++ b/src/i18n/locales/te.ts @@ -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: 'పట్టికలు', diff --git a/src/i18n/locales/tr.ts b/src/i18n/locales/tr.ts index 30d4ceee..f9d0481a 100644 --- a/src/i18n/locales/tr.ts +++ b/src/i18n/locales/tr.ts @@ -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', diff --git a/src/i18n/locales/uk.ts b/src/i18n/locales/uk.ts index 93f0f3c7..aa83b812 100644 --- a/src/i18n/locales/uk.ts +++ b/src/i18n/locales/uk.ts @@ -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: 'Таблиці', diff --git a/src/i18n/locales/vi.ts b/src/i18n/locales/vi.ts index 36ecea09..bba64051 100644 --- a/src/i18n/locales/vi.ts +++ b/src/i18n/locales/vi.ts @@ -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', diff --git a/src/i18n/locales/zh_CN.ts b/src/i18n/locales/zh_CN.ts index a621557e..caa80920 100644 --- a/src/i18n/locales/zh_CN.ts +++ b/src/i18n/locales/zh_CN.ts @@ -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: '表', diff --git a/src/i18n/locales/zh_TW.ts b/src/i18n/locales/zh_TW.ts index 3d2d8d9a..34fbd30c 100644 --- a/src/i18n/locales/zh_TW.ts +++ b/src/i18n/locales/zh_TW.ts @@ -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: '表格', diff --git a/src/lib/domain/config.ts b/src/lib/domain/config.ts index 48c10b8b..06e4e3bc 100644 --- a/src/lib/domain/config.ts +++ b/src/lib/domain/config.ts @@ -1,5 +1,4 @@ export interface ChartDBConfig { defaultDiagramId: string; exportActions?: Date[]; - hiddenTablesByDiagram?: Record; // Maps diagram ID to array of hidden table IDs } diff --git a/src/lib/domain/db-dependency.ts b/src/lib/domain/db-dependency.ts index 753dabae..347194e2 100644 --- a/src/lib/domain/db-dependency.ts +++ b/src/lib/domain/db-dependency.ts @@ -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 = 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.POSTGRESQL]: 'postgresql', [DatabaseType.MYSQL]: 'postgresql', diff --git a/src/lib/domain/db-relationship.ts b/src/lib/domain/db-relationship.ts index 4bb076ec..fd5debd2 100644 --- a/src/lib/domain/db-relationship.ts +++ b/src/lib/domain/db-relationship.ts @@ -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 diff --git a/src/lib/domain/db-table.ts b/src/lib/domain/db-table.ts index 2d55a05a..0bb0d932 100644 --- a/src/lib/domain/db-table.ts +++ b/src/lib/domain/db-table.ts @@ -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 diff --git a/src/lib/domain/diagram-filter/diagram-filter.ts b/src/lib/domain/diagram-filter/diagram-filter.ts new file mode 100644 index 00000000..c58c60e5 --- /dev/null +++ b/src/lib/domain/diagram-filter/diagram-filter.ts @@ -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(); + 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, + }; +} diff --git a/src/lib/domain/diagram-filter/filter.ts b/src/lib/domain/diagram-filter/filter.ts new file mode 100644 index 00000000..ce283fac --- /dev/null +++ b/src/lib/domain/diagram-filter/filter.ts @@ -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; diff --git a/src/pages/editor-page/canvas/canvas-context-menu.tsx b/src/pages/editor-page/canvas/canvas-context-menu.tsx index fb259647..594b704a 100644 --- a/src/pages/editor-page/canvas/canvas-context-menu.tsx +++ b/src/pages/editor-page/canvas/canvas-context-menu.tsx @@ -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 = ({ 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 = ({ 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 = ({ 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 = ({ createTable, screenToFlowPosition, openTableSchemaDialog, - schemas, - filteredSchemas, + schemasDisplayed, ] ); diff --git a/src/pages/editor-page/canvas/canvas-filter/canvas-filter.tsx b/src/pages/editor-page/canvas/canvas-filter/canvas-filter.tsx index 005d559c..ef502b6d 100644 --- a/src/pages/editor-page/canvas/canvas-filter/canvas-filter.tsx +++ b/src/pages/editor-page/canvas/canvas-filter/canvas-filter.tsx @@ -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 = ({ 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>({}); @@ -69,14 +69,21 @@ export const CanvasFilter: React.FC = ({ onClose }) => { [tables] ); + const databaseWithSchemas = useMemo( + () => !!defaultSchemas[databaseType], + [databaseType] + ); + // Convert tables to tree nodes const treeData = useMemo(() => { // Group tables by schema const tablesBySchema = new Map(); 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 = ({ onClose }) => { const nodes: TreeNode[] = []; 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 = { 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 => { - 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 = ({ onClose }) => { icon: Table, context: { tableSchema: table.schema, - hidden: tableHidden, + visible: tableVisible, }, className: hidden ? 'opacity-50' : '', }; @@ -134,7 +165,7 @@ export const CanvasFilter: React.FC = ({ 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 = ({ 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 = ({ 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 (