mirror of
				https://github.com/chartdb/chartdb.git
				synced 2025-10-31 03:53:55 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			v1.14.0
			...
			jf/refacto
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | d1fbab31bc | ||
|  | 4dd2a5742e | ||
|  | fb54d73465 | 
| @@ -1,4 +1,11 @@ | ||||
| import React, { type ReactNode, useCallback, useState } from 'react'; | ||||
| import React, { | ||||
|     type ReactNode, | ||||
|     useCallback, | ||||
|     useState, | ||||
|     useMemo, | ||||
|     useEffect, | ||||
|     useRef, | ||||
| } from 'react'; | ||||
| import { canvasContext } from './canvas-context'; | ||||
| import { useChartDB } from '@/hooks/use-chartdb'; | ||||
| import { | ||||
| @@ -15,13 +22,49 @@ interface CanvasProviderProps { | ||||
| } | ||||
|  | ||||
| export const CanvasProvider = ({ children }: CanvasProviderProps) => { | ||||
|     const { tables, relationships, updateTablesState, filteredSchemas } = | ||||
|         useChartDB(); | ||||
|     const { | ||||
|         tables, | ||||
|         relationships, | ||||
|         updateTablesState, | ||||
|         filteredSchemas, | ||||
|         hiddenTableIds, | ||||
|         schemas, | ||||
|     } = useChartDB(); | ||||
|     const { fitView } = useReactFlow(); | ||||
|     const [overlapGraph, setOverlapGraph] = | ||||
|         useState<Graph<string>>(createGraph()); | ||||
|  | ||||
|     // Check if there are any filtered items to determine initial showFilter state | ||||
|     const hasFilteredItems = useMemo(() => { | ||||
|         const hasHiddenTables = (hiddenTableIds ?? []).length > 0; | ||||
|         const hasSchemasFilter = | ||||
|             filteredSchemas && | ||||
|             schemas.length > 0 && | ||||
|             filteredSchemas.length < schemas.length; | ||||
|         return hasHiddenTables || hasSchemasFilter; | ||||
|     }, [filteredSchemas, hiddenTableIds, schemas]); | ||||
|  | ||||
|     const [showFilter, setShowFilter] = useState(false); | ||||
|     const hasInitialized = useRef(false); | ||||
|  | ||||
|     // Only auto-show filter on initial load if there are filtered items | ||||
|     // Wait for data to be defined (not just empty arrays) before initializing | ||||
|     useEffect(() => { | ||||
|         const dataLoaded = | ||||
|             filteredSchemas !== undefined && hiddenTableIds !== undefined; | ||||
|  | ||||
|         if (!hasInitialized.current && dataLoaded) { | ||||
|             // Add 2 seconds delay to ensure all data is fully loaded | ||||
|             const timer = setTimeout(() => { | ||||
|                 if (hasFilteredItems) { | ||||
|                     setShowFilter(true); | ||||
|                 } | ||||
|                 hasInitialized.current = true; | ||||
|             }, 2000); | ||||
|  | ||||
|             return () => clearTimeout(timer); | ||||
|         } | ||||
|     }, [hasFilteredItems, filteredSchemas, hiddenTableIds]); | ||||
|  | ||||
|     const reorderTables = useCallback( | ||||
|         ( | ||||
|   | ||||
| @@ -156,17 +156,18 @@ export const ChartDBProvider: React.FC< | ||||
|             return undefined; | ||||
|         } | ||||
|  | ||||
|         const schemasFilterFromCache = | ||||
|             (schemasFilter[diagramId] ?? []).length === 0 | ||||
|                 ? undefined // in case of empty filter, skip cache | ||||
|                 : schemasFilter[diagramId]; | ||||
|         const schemasFilterFromCache = schemasFilter[diagramId]; | ||||
|  | ||||
|         return ( | ||||
|             schemasFilterFromCache ?? [ | ||||
|                 schemas.find((s) => s.name === defaultSchemaName)?.id ?? | ||||
|                     schemas[0]?.id, | ||||
|             ] | ||||
|         ); | ||||
|         // If there's an explicit filter set (even if empty), use it | ||||
|         if (schemasFilterFromCache !== undefined) { | ||||
|             return schemasFilterFromCache; | ||||
|         } | ||||
|  | ||||
|         // Only default to showing schemas if no filter has been set | ||||
|         return [ | ||||
|             schemas.find((s) => s.name === defaultSchemaName)?.id ?? | ||||
|                 schemas[0]?.id, | ||||
|         ]; | ||||
|     }, [schemasFilter, diagramId, schemas, defaultSchemaName]); | ||||
|  | ||||
|     const currentDiagram: Diagram = useMemo( | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -22,11 +22,6 @@ export interface LocalConfigContext { | ||||
|     showFieldAttributes: boolean; | ||||
|     setShowFieldAttributes: (showFieldAttributes: boolean) => void; | ||||
|  | ||||
|     hideMultiSchemaNotification: boolean; | ||||
|     setHideMultiSchemaNotification: ( | ||||
|         hideMultiSchemaNotification: boolean | ||||
|     ) => void; | ||||
|  | ||||
|     githubRepoOpened: boolean; | ||||
|     setGithubRepoOpened: (githubRepoOpened: boolean) => void; | ||||
|  | ||||
| @@ -56,9 +51,6 @@ export const LocalConfigContext = createContext<LocalConfigContext>({ | ||||
|     showFieldAttributes: true, | ||||
|     setShowFieldAttributes: emptyFn, | ||||
|  | ||||
|     hideMultiSchemaNotification: false, | ||||
|     setHideMultiSchemaNotification: emptyFn, | ||||
|  | ||||
|     githubRepoOpened: false, | ||||
|     setGithubRepoOpened: emptyFn, | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,6 @@ 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'; | ||||
| @@ -40,12 +39,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 +70,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]); | ||||
| @@ -127,8 +113,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({ | ||||
|                 setShowCardinality, | ||||
|                 showFieldAttributes, | ||||
|                 setShowFieldAttributes, | ||||
|                 hideMultiSchemaNotification, | ||||
|                 setHideMultiSchemaNotification, | ||||
|                 setGithubRepoOpened, | ||||
|                 githubRepoOpened, | ||||
|                 starUsDialogLastOpen, | ||||
|   | ||||
| @@ -128,6 +128,8 @@ export const ar: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -129,6 +129,8 @@ export const bn: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -130,6 +130,8 @@ export const de: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -125,6 +125,8 @@ export const en = { | ||||
|                 collapse: 'Collapse All', | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|  | ||||
|   | ||||
| @@ -119,6 +119,8 @@ export const es: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -118,6 +118,8 @@ export const fr: LanguageTranslation = { | ||||
|                 clear: 'Effacer le Filtre', | ||||
|                 no_results: | ||||
|                     'Aucune table trouvée correspondant à votre filtre.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 show_list: 'Afficher la Liste des Tableaux', | ||||
|                 show_dbml: "Afficher l'éditeur DBML", | ||||
|  | ||||
|   | ||||
| @@ -129,6 +129,8 @@ export const gu: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -129,6 +129,8 @@ export const hi: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -126,6 +126,8 @@ export const hr: LanguageTranslation = { | ||||
|                 clear: 'Očisti filter', | ||||
|                 no_results: | ||||
|                     'Nema pronađenih tablica koje odgovaraju vašem filteru.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 show_list: 'Prikaži popis tablica', | ||||
|                 show_dbml: 'Prikaži DBML uređivač', | ||||
|  | ||||
|   | ||||
| @@ -128,6 +128,8 @@ export const id_ID: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -132,6 +132,8 @@ export const ja: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -128,6 +128,8 @@ export const ko_KR: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -131,6 +131,8 @@ export const mr: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -130,6 +130,9 @@ export const ne: LanguageTranslation = { | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 // TODO: Translate | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|  | ||||
|   | ||||
| @@ -129,6 +129,8 @@ export const pt_BR: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -127,6 +127,8 @@ export const ru: LanguageTranslation = { | ||||
|  | ||||
|                 no_results: | ||||
|                     'Таблицы не найдены, соответствующие вашему фильтру.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 show_list: 'Переключиться на список таблиц', | ||||
|                 show_dbml: 'Переключиться на редактор DBML', | ||||
|  | ||||
|   | ||||
| @@ -129,6 +129,8 @@ export const te: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -128,6 +128,8 @@ export const tr: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -127,6 +127,8 @@ export const uk: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -128,6 +128,8 @@ export const vi: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -125,6 +125,8 @@ export const zh_CN: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -125,6 +125,8 @@ export const zh_TW: LanguageTranslation = { | ||||
|                 // TODO: Translate | ||||
|                 clear: 'Clear Filter', | ||||
|                 no_results: 'No tables found matching your filter.', | ||||
|                 all_tables_filtered: 'All tables are filtered.', | ||||
|                 open_filter: 'Open Filter', | ||||
|                 // TODO: Translate | ||||
|                 show_list: 'Show Table List', | ||||
|                 show_dbml: 'Show DBML Editor', | ||||
|   | ||||
| @@ -93,9 +93,18 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => { | ||||
|  | ||||
|         tablesBySchema.forEach((schemaTables, schemaName) => { | ||||
|             const schemaId = schemaNameToSchemaId(schemaName); | ||||
|             const schemaHidden = filteredSchemas | ||||
|             const schemaFilteredOut = filteredSchemas | ||||
|                 ? !filteredSchemas.includes(schemaId) | ||||
|                 : false; | ||||
|  | ||||
|             // Pre-calculate if all tables in this schema are hidden | ||||
|             const allTablesHidden = schemaTables.every( | ||||
|                 (table) => hiddenTableIds?.includes(table.id) ?? false | ||||
|             ); | ||||
|  | ||||
|             // Schema appears hidden if filtered out OR all tables are hidden | ||||
|             const schemaHidden = schemaFilteredOut || allTablesHidden; | ||||
|  | ||||
|             const schemaNode: TreeNode<NodeType, NodeContext> = { | ||||
|                 id: `schema-${schemaName}`, | ||||
|                 name: `${schemaName} (${schemaTables.length})`, | ||||
| @@ -136,13 +145,24 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => { | ||||
|         return nodes; | ||||
|     }, [relevantTableData, databaseType, hiddenTableIds, filteredSchemas]); | ||||
|  | ||||
|     // Initialize expanded state with all schemas expanded | ||||
|     useMemo(() => { | ||||
|         const initialExpanded: Record<string, boolean> = {}; | ||||
|         treeData.forEach((node) => { | ||||
|             initialExpanded[node.id] = true; | ||||
|     // Initialize expanded state - collapse if multiple schemas, expand if single schema | ||||
|     useEffect(() => { | ||||
|         setExpanded((prevExpanded) => { | ||||
|             const hasMultipleSchemas = treeData.length > 1; | ||||
|             const newExpanded: Record<string, boolean> = {}; | ||||
|  | ||||
|             treeData.forEach((node) => { | ||||
|                 // Preserve existing expanded state if it exists, otherwise set based on schema count | ||||
|                 if (node.id in prevExpanded) { | ||||
|                     newExpanded[node.id] = prevExpanded[node.id]; | ||||
|                 } else { | ||||
|                     // If there are multiple schemas, start collapsed; otherwise expanded | ||||
|                     newExpanded[node.id] = !hasMultipleSchemas; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             return newExpanded; | ||||
|         }); | ||||
|         setExpanded(initialExpanded); | ||||
|     }, [treeData]); | ||||
|  | ||||
|     // Filter tree data based on search query | ||||
| @@ -222,10 +242,19 @@ 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 | ||||
|                 const schemaFilteredOut = filteredSchemas | ||||
|                     ? !filteredSchemas.includes(schemaId) | ||||
|                     : false; | ||||
|  | ||||
|                 // Check if all tables in this schema are hidden | ||||
|                 const allTablesHidden = | ||||
|                     node.children?.every((child) => | ||||
|                         hiddenTableIds?.includes(child.id) | ||||
|                     ) ?? false; | ||||
|  | ||||
|                 // Schema is "hidden" if filtered out OR all tables are hidden | ||||
|                 const schemaHidden = schemaFilteredOut || allTablesHidden; | ||||
|  | ||||
|                 return ( | ||||
|                     <Button | ||||
|                         variant="ghost" | ||||
| @@ -233,21 +262,26 @@ 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, | ||||
|                                 ]); | ||||
|                                 // Show the schema in filter | ||||
|                                 if (schemaFilteredOut) { | ||||
|                                     filterSchemas([ | ||||
|                                         ...(filteredSchemas ?? []), | ||||
|                                         schemaId, | ||||
|                                     ]); | ||||
|                                 } | ||||
|                                 // Unhide all tables in this schema | ||||
|                                 node.children?.forEach((child) => { | ||||
|                                     if ( | ||||
|                                         child.type === 'table' && | ||||
|                                         hiddenTableIds?.includes(child.id) | ||||
|                                     ) { | ||||
|                                         removeHiddenTableId(child.id); | ||||
|                                     } | ||||
|                                 }); | ||||
|                             } else { | ||||
|                                 // Hide the schema and all its tables | ||||
|                                 filterSchemas( | ||||
|                                     filteredSchemas?.filter( | ||||
|                                         (s) => s !== schemaId | ||||
| @@ -308,6 +342,39 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => { | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 toggleTableVisibility(tableId, !hidden); | ||||
|  | ||||
|                                 // Check if this was the last visible table in the schema | ||||
|                                 if (!hidden && tableSchema) { | ||||
|                                     const schemaNode = treeData.find( | ||||
|                                         (s) => | ||||
|                                             (s.context as SchemaContext) | ||||
|                                                 .name === tableSchema | ||||
|                                     ); | ||||
|                                     if (schemaNode) { | ||||
|                                         // Check if all other tables in this schema will be hidden | ||||
|                                         const willAllBeHidden = | ||||
|                                             schemaNode.children?.every( | ||||
|                                                 (child) => | ||||
|                                                     child.id === tableId || | ||||
|                                                     hiddenTableIds?.includes( | ||||
|                                                         child.id | ||||
|                                                     ) | ||||
|                                             ) ?? false; | ||||
|  | ||||
|                                         if (willAllBeHidden) { | ||||
|                                             // Hide the schema as well | ||||
|                                             const schemaId = | ||||
|                                                 schemaNameToSchemaId( | ||||
|                                                     tableSchema | ||||
|                                                 ); | ||||
|                                             filterSchemas( | ||||
|                                                 filteredSchemas?.filter( | ||||
|                                                     (s) => s !== schemaId | ||||
|                                                 ) ?? [] | ||||
|                                             ); | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         }} | ||||
|                     > | ||||
| @@ -336,7 +403,13 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => { | ||||
|     // Handle node click | ||||
|     const handleNodeClick = useCallback( | ||||
|         (node: TreeNode<NodeType, NodeContext>) => { | ||||
|             if (node.type === 'table') { | ||||
|             if (node.type === 'schema') { | ||||
|                 // Toggle schema expansion on single click | ||||
|                 setExpanded((prev) => ({ | ||||
|                     ...prev, | ||||
|                     [node.id]: !prev[node.id], | ||||
|                 })); | ||||
|             } else if (node.type === 'table') { | ||||
|                 const tableContext = node.context as TableContext; | ||||
|                 const tableSchema = tableContext.tableSchema; | ||||
|                 const visibleBySchema = shouldShowTableSchemaBySchemaFilter({ | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { useCallback, useState } from 'react'; | ||||
| import React, { useCallback, useState, useMemo } from 'react'; | ||||
| import { Card, CardContent } from '@/components/card/card'; | ||||
| import { ZoomIn, ZoomOut, Funnel, Redo, Undo, Scan } from 'lucide-react'; | ||||
| import { Separator } from '@/components/separator/separator'; | ||||
| @@ -30,12 +30,26 @@ 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 { hiddenTableIds, filteredSchemas, schemas } = useChartDB(); | ||||
|  | ||||
|     const toggleFilter = useCallback(() => { | ||||
|         setShowFilter((prev) => !prev); | ||||
|     }, [setShowFilter]); | ||||
|  | ||||
|     // Check if any filtering is active | ||||
|     const hasActiveFilter = useMemo(() => { | ||||
|         // Check if any tables are hidden | ||||
|         const hasHiddenTables = (hiddenTableIds ?? []).length > 0; | ||||
|  | ||||
|         // Check if schemas are being filtered | ||||
|         const hasSchemasFilter = | ||||
|             filteredSchemas && | ||||
|             schemas.length > 0 && | ||||
|             filteredSchemas.length < schemas.length; | ||||
|  | ||||
|         return hasHiddenTables || hasSchemasFilter; | ||||
|     }, [hiddenTableIds, filteredSchemas, schemas]); | ||||
|  | ||||
|     useOnViewportChange({ | ||||
|         onChange: ({ zoom }) => { | ||||
|             setZoom(convertToPercentage(zoom)); | ||||
| @@ -80,8 +94,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'; | ||||
| @@ -43,21 +38,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 +70,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> | ||||
|   | ||||
| @@ -19,7 +19,8 @@ import { useLayout } from '@/hooks/use-layout'; | ||||
| export interface DependenciesSectionProps {} | ||||
|  | ||||
| export const DependenciesSection: React.FC<DependenciesSectionProps> = () => { | ||||
|     const { dependencies, filteredSchemas, getTable } = useChartDB(); | ||||
|     const { dependencies, filteredSchemas, hiddenTableIds, getTable } = | ||||
|         useChartDB(); | ||||
|     const [filterText, setFilterText] = React.useState(''); | ||||
|     const { closeAllDependenciesInSidebar } = useLayout(); | ||||
|     const { t } = useTranslation(); | ||||
| @@ -44,12 +45,15 @@ export const DependenciesSection: React.FC<DependenciesSectionProps> = () => { | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|         const filterSchema: (dependency: DBDependency) => boolean = ( | ||||
|         const filterVisible: (dependency: DBDependency) => boolean = ( | ||||
|             dependency | ||||
|         ) => shouldShowDependencyBySchemaFilter(dependency, filteredSchemas); | ||||
|         ) => | ||||
|             shouldShowDependencyBySchemaFilter(dependency, filteredSchemas) && | ||||
|             !hiddenTableIds?.includes(dependency.tableId) && | ||||
|             !hiddenTableIds?.includes(dependency.dependentTableId); | ||||
|  | ||||
|         return dependencies | ||||
|             .filter(filterSchema) | ||||
|             .filter(filterVisible) | ||||
|             .filter(filterName) | ||||
|             .sort((a, b) => { | ||||
|                 const dependentTableA = getTable(a.dependentTableId); | ||||
| @@ -60,7 +64,7 @@ export const DependenciesSection: React.FC<DependenciesSectionProps> = () => { | ||||
|                     `${dependentTableB?.name}${tableB?.name}` | ||||
|                 ); | ||||
|             }); | ||||
|     }, [dependencies, filterText, filteredSchemas, getTable]); | ||||
|     }, [dependencies, filterText, filteredSchemas, hiddenTableIds, getTable]); | ||||
|  | ||||
|     return ( | ||||
|         <section className="flex flex-1 flex-col overflow-hidden px-2"> | ||||
|   | ||||
| @@ -20,7 +20,7 @@ import { useDialog } from '@/hooks/use-dialog'; | ||||
| export interface RelationshipsSectionProps {} | ||||
|  | ||||
| export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => { | ||||
|     const { relationships, filteredSchemas } = useChartDB(); | ||||
|     const { relationships, filteredSchemas, hiddenTableIds } = useChartDB(); | ||||
|     const [filterText, setFilterText] = React.useState(''); | ||||
|     const { closeAllRelationshipsInSidebar } = useLayout(); | ||||
|     const { t } = useTranslation(); | ||||
| @@ -34,13 +34,18 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => { | ||||
|             !filterText?.trim?.() || | ||||
|             relationship.name.toLowerCase().includes(filterText.toLowerCase()); | ||||
|  | ||||
|         const filterSchema: (relationship: DBRelationship) => boolean = ( | ||||
|         const filterVisible: (relationship: DBRelationship) => boolean = ( | ||||
|             relationship | ||||
|         ) => | ||||
|             shouldShowRelationshipBySchemaFilter(relationship, filteredSchemas); | ||||
|             shouldShowRelationshipBySchemaFilter( | ||||
|                 relationship, | ||||
|                 filteredSchemas | ||||
|             ) && | ||||
|             !hiddenTableIds?.includes(relationship.sourceTableId) && | ||||
|             !hiddenTableIds?.includes(relationship.targetTableId); | ||||
|  | ||||
|         return relationships.filter(filterSchema).filter(filterName); | ||||
|     }, [relationships, filterText, filteredSchemas]); | ||||
|         return relationships.filter(filterVisible).filter(filterName); | ||||
|     }, [relationships, filterText, filteredSchemas, hiddenTableIds]); | ||||
|  | ||||
|     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 | ||||
|   | ||||
| @@ -55,7 +55,6 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({ | ||||
|         createField, | ||||
|         createTable, | ||||
|         schemas, | ||||
|         filteredSchemas, | ||||
|         databaseType, | ||||
|     } = useChartDB(); | ||||
|     const { openTableSchemaDialog } = useDialog(); | ||||
| @@ -267,7 +266,7 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({ | ||||
|  | ||||
|     let schemaToDisplay; | ||||
|  | ||||
|     if (schemas.length > 1 && !!filteredSchemas && filteredSchemas.length > 1) { | ||||
|     if (schemas.length > 1) { | ||||
|         schemaToDisplay = table.schema; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import React, { useCallback, useMemo, useState } from 'react'; | ||||
| import { TableList } from './table-list/table-list'; | ||||
| import { Button } from '@/components/button/button'; | ||||
| import { Table, List, X, Code } from 'lucide-react'; | ||||
| import { Table, List, X, Code, Funnel } from 'lucide-react'; | ||||
| import { Input } from '@/components/input/input'; | ||||
| import type { DBTable } from '@/lib/domain/db-table'; | ||||
| import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table'; | ||||
| @@ -21,11 +21,13 @@ 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 { useCanvas } from '@/hooks/use-canvas'; | ||||
|  | ||||
| export interface TablesSectionProps {} | ||||
|  | ||||
| export const TablesSection: React.FC<TablesSectionProps> = () => { | ||||
|     const { createTable, tables, filteredSchemas, schemas } = useChartDB(); | ||||
|     const { createTable, tables, hiddenTableIds, filteredSchemas, schemas } = | ||||
|         useChartDB(); | ||||
|     const { openTableSchemaDialog } = useDialog(); | ||||
|     const viewport = useViewport(); | ||||
|     const { t } = useTranslation(); | ||||
| @@ -33,17 +35,32 @@ export const TablesSection: React.FC<TablesSectionProps> = () => { | ||||
|     const [filterText, setFilterText] = React.useState(''); | ||||
|     const [showDBML, setShowDBML] = useState(false); | ||||
|     const filterInputRef = React.useRef<HTMLInputElement>(null); | ||||
|     const { setShowFilter } = useCanvas(); | ||||
|  | ||||
|     const filteredTables = useMemo(() => { | ||||
|         const filterTableName: (table: DBTable) => boolean = (table) => | ||||
|             !filterText?.trim?.() || | ||||
|             table.name.toLowerCase().includes(filterText.toLowerCase()); | ||||
|  | ||||
|         const filterSchema: (table: DBTable) => boolean = (table) => | ||||
|         // Show only tables that are visible on the canvas | ||||
|         const filterVisible: (table: DBTable) => boolean = (table) => | ||||
|             !hiddenTableIds?.includes(table.id) && | ||||
|             shouldShowTablesBySchemaFilter(table, filteredSchemas); | ||||
|  | ||||
|         return tables.filter(filterSchema).filter(filterTableName); | ||||
|     }, [tables, filterText, filteredSchemas]); | ||||
|         return tables.filter(filterVisible).filter(filterTableName); | ||||
|     }, [tables, filterText, hiddenTableIds, filteredSchemas]); | ||||
|  | ||||
|     // Check if all tables are filtered out by canvas filters (not text filter) | ||||
|     const allTablesFilteredByCanvas = useMemo(() => { | ||||
|         return ( | ||||
|             tables.length > 0 && | ||||
|             tables.every( | ||||
|                 (table) => | ||||
|                     hiddenTableIds?.includes(table.id) || | ||||
|                     !shouldShowTablesBySchemaFilter(table, filteredSchemas) | ||||
|             ) | ||||
|         ); | ||||
|     }, [tables, hiddenTableIds, filteredSchemas]); | ||||
|  | ||||
|     const createTableWithLocation = useCallback( | ||||
|         async ({ schema }: { schema?: DBSchema }) => { | ||||
| @@ -97,6 +114,10 @@ export const TablesSection: React.FC<TablesSectionProps> = () => { | ||||
|         setFilterText(''); | ||||
|     }, []); | ||||
|  | ||||
|     const handleOpenCanvasFilter = useCallback(() => { | ||||
|         setShowFilter(true); | ||||
|     }, [setShowFilter]); | ||||
|  | ||||
|     const operatingSystem = useMemo(() => getOperatingSystem(), []); | ||||
|  | ||||
|     useHotkeys( | ||||
| @@ -177,6 +198,23 @@ export const TablesSection: React.FC<TablesSectionProps> = () => { | ||||
|                                 )} | ||||
|                                 className="mt-20" | ||||
|                             /> | ||||
|                         ) : allTablesFilteredByCanvas ? ( | ||||
|                             <div className="mt-10 flex flex-col items-center gap-2"> | ||||
|                                 <div className="text-sm text-muted-foreground"> | ||||
|                                     {t( | ||||
|                                         'side_panel.tables_section.all_tables_filtered' | ||||
|                                     )} | ||||
|                                 </div> | ||||
|                                 <Button | ||||
|                                     variant="outline" | ||||
|                                     size="sm" | ||||
|                                     onClick={handleOpenCanvasFilter} | ||||
|                                     className="gap-1" | ||||
|                                 > | ||||
|                                     <Funnel className="size-3.5" /> | ||||
|                                     {t('side_panel.tables_section.open_filter')} | ||||
|                                 </Button> | ||||
|                             </div> | ||||
|                         ) : filterText && filteredTables.length === 0 ? ( | ||||
|                             <div className="mt-10 flex flex-col items-center gap-2"> | ||||
|                                 <div className="text-sm text-muted-foreground"> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user