filter by schema

This commit is contained in:
Guy Ben-Aharon
2024-09-08 17:43:11 +03:00
committed by Guy Ben-Aharon
parent 0e6ebf3ecd
commit 50ef457fe2
21 changed files with 590 additions and 96 deletions
+293
View File
@@ -0,0 +1,293 @@
import { CaretSortIcon, CheckIcon, Cross2Icon } from '@radix-ui/react-icons';
import * as React from 'react';
import { cn } from '@/lib/utils';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/command/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/popover/popover';
import { ScrollArea } from '@/components/scroll-area/scroll-area';
export interface SelectBoxOption {
value: string;
label: string;
description?: string;
}
interface SelectBoxProps {
options: SelectBoxOption[];
value?: string[] | string;
onChange?: (values: string[] | string) => void;
placeholder?: string;
inputPlaceholder?: string;
emptyPlaceholder?: string;
className?: string;
multiple?: boolean;
oneLine?: boolean;
selectAll?: boolean;
deselectAll?: boolean;
onSelectAll?: () => void;
onDeselectAll?: () => void;
}
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
(
{
inputPlaceholder,
emptyPlaceholder,
placeholder,
className,
options,
value,
onChange,
multiple,
oneLine,
selectAll,
deselectAll,
onSelectAll,
onDeselectAll,
},
ref
) => {
const [searchTerm, setSearchTerm] = React.useState<string>('');
const [isOpen, setIsOpen] = React.useState(false);
const handleSelect = React.useCallback(
(selectedValue: string) => {
if (multiple) {
const newValue =
value?.includes(selectedValue) && Array.isArray(value)
? value.filter((v) => v !== selectedValue)
: [...(value ?? []), selectedValue];
onChange?.(newValue);
} else {
onChange?.(selectedValue);
setIsOpen(false);
}
},
[multiple, onChange, value]
);
const handleClear = React.useCallback(() => {
onChange?.(multiple ? [] : '');
}, [multiple, onChange]);
const selectedMultipleOptions = React.useMemo(
() =>
options
.filter(
(option) =>
Array.isArray(value) && value.includes(option.value)
)
.map((option) => (
<span
key={option.value}
className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`}
>
<span>{option.label}</span>
<span
onClick={(e) => {
e.preventDefault();
handleSelect(option.value);
}}
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
>
<Cross2Icon />
</span>
</span>
)),
[options, value, handleSelect, oneLine]
);
const isAllSelected = React.useMemo(
() =>
multiple &&
Array.isArray(value) &&
options.every((option) => value.includes(option.value)),
[options, value, multiple]
);
return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<div
className={cn(
'flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring',
className
)}
>
<div
className={cn(
'items-center gap-1 overflow-hidden text-sm',
multiple
? 'flex flex-grow flex-wrap'
: 'inline-flex whitespace-nowrap'
)}
>
{value && value.length > 0 ? (
multiple ? (
oneLine ? (
<div className="block w-full min-w-0 shrink-0 truncate">
{selectedMultipleOptions}
</div>
) : (
selectedMultipleOptions
)
) : (
options.find((opt) => opt.value === value)
?.label
)
) : (
<span className="mr-auto text-muted-foreground">
{placeholder}
</span>
)}
</div>
<div className="flex items-center self-stretch pl-1 text-muted-foreground/60 hover:text-foreground [&>div]:flex [&>div]:items-center [&>div]:self-stretch">
{value && value.length > 0 ? (
<div
onClick={(e) => {
e.preventDefault();
handleClear();
}}
>
<span className="text-xs">Clear</span>
{/* <Cross2Icon className="size-4" /> */}
</div>
) : (
<div>
<CaretSortIcon className="size-4" />
</div>
)}
</div>
</div>
</PopoverTrigger>
<PopoverContent
className="w-[var(--radix-popover-trigger-width)] p-0"
align="start"
>
<Command>
<div className="relative">
<CommandInput
value={searchTerm}
onValueChange={(e) => setSearchTerm(e)}
ref={ref}
placeholder={inputPlaceholder ?? 'Search...'}
className="h-9"
/>
{searchTerm && (
<div
className="absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3 text-muted-foreground hover:text-foreground"
onClick={() => setSearchTerm('')}
>
<Cross2Icon className="size-4" />
</div>
)}
{!searchTerm &&
multiple &&
selectAll &&
!isAllSelected && (
<div
className="absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3 text-xs text-muted-foreground hover:text-foreground"
onClick={() => onSelectAll?.()}
>
Select All
</div>
)}
{!searchTerm &&
multiple &&
deselectAll &&
isAllSelected && (
<div
className="absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3 text-xs text-muted-foreground hover:text-foreground"
onClick={() => onDeselectAll?.()}
>
Deselect All
</div>
)}
</div>
<CommandEmpty>
{emptyPlaceholder ?? 'No results found.'}
</CommandEmpty>
<ScrollArea>
<div className="max-h-64 w-full">
<CommandGroup>
<CommandList className="max-h-64 w-full">
{options.map((option) => {
const isSelected =
Array.isArray(value) &&
value.includes(option.value);
return (
<CommandItem
className="flex items-center"
key={option.value}
// value={option.value}
onSelect={() =>
handleSelect(
option.value
)
}
>
{multiple && (
<div
className={cn(
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
isSelected
? 'bg-primary text-primary-foreground'
: 'opacity-50 [&_svg]:invisible'
)}
>
<CheckIcon />
</div>
)}
<div className="flex items-center">
<span>
{option.label}
</span>
{option.description && (
<span className="ml-1 text-xs text-muted-foreground">
{
option.description
}
</span>
)}
</div>
{!multiple &&
option.value ===
value && (
<CheckIcon
className={cn(
'ml-auto',
option.value ===
value
? 'opacity-100'
: 'opacity-0'
)}
/>
)}
</CommandItem>
);
})}
</CommandList>
</CommandGroup>
</div>
</ScrollArea>
</Command>
</PopoverContent>
</Popover>
);
}
);
SelectBox.displayName = 'SelectBox';
@@ -7,15 +7,20 @@ import { DBIndex } from '@/lib/domain/db-index';
import { DBRelationship } from '@/lib/domain/db-relationship';
import { Diagram } from '@/lib/domain/diagram';
import { DatabaseEdition } from '@/lib/domain/database-edition';
import { DBSchema } from '@/lib/domain/db-schema';
export interface ChartDBContext {
diagramId: string;
diagramName: string;
databaseType: DatabaseType;
tables: DBTable[];
schemas: DBSchema[];
relationships: DBRelationship[];
currentDiagram: Diagram;
filteredSchemas?: string[];
filterSchemas: (schemaIds: string[]) => void;
// General operations
updateDiagramId: (id: string) => Promise<void>;
updateDiagramName: (
@@ -129,6 +134,9 @@ export const chartDBContext = createContext<ChartDBContext>({
diagramId: '',
tables: [],
relationships: [],
schemas: [],
filteredSchemas: [],
filterSchemas: emptyFn,
currentDiagram: {
id: '',
name: '',
@@ -13,12 +13,16 @@ import { Diagram } from '@/lib/domain/diagram';
import { useNavigate } from 'react-router-dom';
import { useConfig } from '@/hooks/use-config';
import { DatabaseEdition } from '@/lib/domain/database-edition';
import { DBSchema, schemaNameToSchemaId } from '@/lib/domain/db-schema';
import { useLocalConfig } from '@/hooks/use-local-config';
import { defaultSchemas } from '@/lib/data/default-schemas';
export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const db = useStorage();
const navigate = useNavigate();
const { setSchemasFilter, schemasFilter } = useLocalConfig();
const { addUndoAction, resetRedoStack, resetUndoStack } =
useRedoUndoStack();
const [diagramId, setDiagramId] = useState('');
@@ -35,6 +39,56 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
const [tables, setTables] = useState<DBTable[]>([]);
const [relationships, setRelationships] = useState<DBRelationship[]>([]);
const defaultSchemaName = defaultSchemas[databaseType];
const schemas = useMemo(
() =>
databaseType === DatabaseType.POSTGRESQL ||
databaseType === DatabaseType.SQL_SERVER
? [
...new Set(
tables
.map((table) => table.schema)
.filter((schema) => !!schema) as string[]
),
]
.sort((a, b) =>
a === defaultSchemaName ? -1 : a.localeCompare(b)
)
.map(
(schema): DBSchema => ({
id: schemaNameToSchemaId(schema),
name: schema,
tableCount: tables.filter(
(table) => table.schema === schema
).length,
})
)
: [],
[tables, defaultSchemaName, databaseType]
);
const filterSchemas: ChartDBContext['filterSchemas'] = useCallback(
(schemaIds) => {
setSchemasFilter((prev) => ({
...prev,
[diagramId]: schemaIds,
}));
},
[diagramId, setSchemasFilter]
);
const filteredSchemas: ChartDBContext['filteredSchemas'] = useMemo(
() =>
schemas.length > 0
? (schemasFilter[diagramId] ?? [
schemas.find((s) => s.name === defaultSchemaName)?.id ??
schemas[0]?.id,
])
: undefined,
[schemasFilter, diagramId, schemas, defaultSchemaName]
);
const currentDiagram: Diagram = useMemo(
() => ({
id: diagramId,
@@ -1010,6 +1064,9 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
tables,
relationships,
currentDiagram,
schemas,
filteredSchemas,
filterSchemas,
updateDiagramId,
updateDiagramName,
loadDiagram,
@@ -0,0 +1,29 @@
import { createContext } from 'react';
import { emptyFn } from '@/lib/utils';
import { Theme } from '../theme-context/theme-context';
export type ScrollAction = 'pan' | 'zoom';
export type SchemasFilter = Record<string, string[]>;
export interface LocalConfigContext {
theme: Theme;
setTheme: (theme: Theme) => void;
scrollAction: ScrollAction;
setScrollAction: (action: ScrollAction) => void;
schemasFilter: SchemasFilter;
setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>;
}
export const LocalConfigContext = createContext<LocalConfigContext>({
theme: 'system',
setTheme: emptyFn,
scrollAction: 'pan',
setScrollAction: emptyFn,
schemasFilter: {},
setSchemasFilter: emptyFn,
});
@@ -0,0 +1,52 @@
import React, { useEffect } from 'react';
import {
LocalConfigContext,
SchemasFilter,
ScrollAction,
} from './local-config-context';
import { Theme } from '../theme-context/theme-context';
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [theme, setTheme] = React.useState<Theme>(
(localStorage.getItem('theme') as Theme) || 'system'
);
const [scrollAction, setScrollAction] = React.useState<ScrollAction>(
(localStorage.getItem('scroll_action') as ScrollAction) || 'pan'
);
const [schemasFilter, setSchemasFilter] = React.useState<SchemasFilter>(
JSON.parse(
localStorage.getItem('schemas_filter') || '{}'
) as SchemasFilter
);
useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);
useEffect(() => {
localStorage.setItem('scroll_action', scrollAction);
}, [scrollAction]);
useEffect(() => {
localStorage.setItem('schemas_filter', JSON.stringify(schemasFilter));
}, [schemasFilter]);
return (
<LocalConfigContext.Provider
value={{
theme,
setTheme,
scrollAction,
setScrollAction,
schemasFilter,
setSchemasFilter,
}}
>
{children}
</LocalConfigContext.Provider>
);
};
@@ -1,14 +0,0 @@
import { createContext } from 'react';
import { emptyFn } from '@/lib/utils';
export type ScrollActionType = 'pan' | 'zoom';
export interface ScrollContext {
scrollAction: ScrollActionType;
setScrollAction: (action: ScrollActionType) => void;
}
export const ScrollContext = createContext<ScrollContext>({
scrollAction: 'pan',
setScrollAction: emptyFn,
});
@@ -1,23 +0,0 @@
import React, { useEffect, useState } from 'react';
import { ScrollContext, ScrollActionType } from './scroll-context';
export const ScrollProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [scrollAction, setScrollAction] = useState<ScrollActionType>(() => {
const savedAction = localStorage.getItem(
'scrollAction'
) as ScrollActionType | null;
return savedAction || 'pan';
});
useEffect(() => {
localStorage.setItem('scrollAction', scrollAction);
}, [scrollAction]);
return (
<ScrollContext.Provider value={{ scrollAction, setScrollAction }}>
{children}
</ScrollContext.Provider>
);
};
+5 -5
View File
@@ -1,13 +1,13 @@
import { createContext } from 'react';
import { emptyFn } from '@/lib/utils';
export type ThemeType = 'light' | 'dark' | 'system';
export type EffectiveThemeType = Exclude<ThemeType, 'system'>;
export type Theme = 'light' | 'dark' | 'system';
export type EffectiveTheme = Exclude<Theme, 'system'>;
export interface ThemeContext {
theme: ThemeType;
setTheme: (theme: ThemeType) => void;
effectiveTheme: EffectiveThemeType;
theme: Theme;
setTheme: (theme: Theme) => void;
effectiveTheme: EffectiveTheme;
}
export const ThemeContext = createContext<ThemeContext>({
+4 -7
View File
@@ -1,14 +1,12 @@
import React, { useEffect, useState } from 'react';
import { EffectiveThemeType, ThemeContext, ThemeType } from './theme-context';
import { EffectiveTheme, ThemeContext } from './theme-context';
import { useMediaQuery } from 'react-responsive';
import { useLocalConfig } from '@/hooks/use-local-config';
export const ThemeProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [theme, setTheme] = useState<ThemeType>(() => {
const savedTheme = localStorage.getItem('theme') as ThemeType | null;
return savedTheme || 'system';
});
const { theme, setTheme } = useLocalConfig();
const isDarkSystemTheme = useMediaQuery({
query: '(prefers-color-scheme: dark)',
});
@@ -16,10 +14,9 @@ export const ThemeProvider: React.FC<React.PropsWithChildren> = ({
const systemTheme = isDarkSystemTheme ? 'dark' : 'light';
const [effectiveTheme, setEffectiveTheme] =
useState<EffectiveThemeType>(systemTheme);
useState<EffectiveTheme>(systemTheme);
useEffect(() => {
localStorage.setItem('theme', theme);
setEffectiveTheme(theme === 'system' ? systemTheme : theme);
}, [theme, systemTheme]);
+4
View File
@@ -0,0 +1,4 @@
import { useContext } from 'react';
import { LocalConfigContext } from '@/context/local-config-context/local-config-context';
export const useLocalConfig = () => useContext(LocalConfigContext);
-4
View File
@@ -1,4 +0,0 @@
import { ScrollContext } from '@/context/scroll-context/scroll-context';
import { useContext } from 'react';
export const useScrollAction = () => useContext(ScrollContext);
+6
View File
@@ -0,0 +1,6 @@
import { DatabaseType } from '../domain/database-type';
export const defaultSchemas: { [key in DatabaseType]?: string } = {
[DatabaseType.POSTGRESQL]: 'public',
[DatabaseType.SQL_SERVER]: 'dbo',
};
+2 -2
View File
@@ -15,7 +15,7 @@ import SqliteLogo2 from '@/assets/sqlite_logo_2.png';
import SqlServerLogo2 from '@/assets/sql_server_logo_2.png';
import GeneralDBLogo2 from '@/assets/general_db_logo_2.png';
import { DatabaseType } from './domain/database-type';
import { EffectiveThemeType } from '@/context/theme-context/theme-context';
import { EffectiveTheme } from '@/context/theme-context/theme-context';
export const databaseTypeToLabelMap: Record<DatabaseType, string> = {
[DatabaseType.GENERIC]: 'Generic',
@@ -46,7 +46,7 @@ export const databaseDarkLogoMap: Record<DatabaseType, string> = {
export const getDatabaseLogo = (
databaseType: DatabaseType,
theme: EffectiveThemeType
theme: EffectiveTheme
) =>
theme === 'dark'
? databaseDarkLogoMap[databaseType]
+8
View File
@@ -0,0 +1,8 @@
export interface DBSchema {
id: string;
name: string;
tableCount: number;
}
export const schemaNameToSchemaId = (schema: string): string =>
schema.toLowerCase().split(' ').join('_');
+1
View File
@@ -22,6 +22,7 @@ export interface DBTable {
createdAt: number;
width?: number;
comments?: string;
hidden?: boolean;
}
export const createTablesFromMetadata = ({
+21 -8
View File
@@ -31,13 +31,17 @@ import { Badge } from '@/components/badge/badge';
import { useTheme } from '@/hooks/use-theme';
import { useTranslation } from 'react-i18next';
import { DBTable } from '@/lib/domain/db-table';
import { useScrollAction } from '@/hooks/use-scroll-action';
import { useLocalConfig } from '@/hooks/use-local-config';
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
type AddEdgeParams = Parameters<typeof addEdge<TableEdgeType>>[0];
const initialEdges: TableEdgeType[] = [];
const tableToTableNode = (table: DBTable): TableNodeType => ({
const tableToTableNode = (
table: DBTable,
filteredSchemas?: string[]
): TableNodeType => ({
id: table.id,
type: 'table',
position: { x: table.x, y: table.y },
@@ -45,6 +49,10 @@ const tableToTableNode = (table: DBTable): TableNodeType => ({
table,
},
width: table.width ?? MIN_TABLE_SIZE,
hidden:
!!table.schema &&
!!filteredSchemas &&
!filteredSchemas.includes(schemaNameToSchemaId(table.schema)),
});
export interface CanvasProps {
@@ -53,6 +61,7 @@ export interface CanvasProps {
export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
const { getEdge, getInternalNode, fitView } = useReactFlow();
const { filteredSchemas } = useChartDB();
const { toast } = useToast();
const { t } = useTranslation();
const {
@@ -65,14 +74,14 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
} = useChartDB();
const { showSidePanel } = useLayout();
const { effectiveTheme } = useTheme();
const { scrollAction } = useScrollAction();
const { scrollAction } = useLocalConfig();
const { isMd: isDesktop } = useBreakpoint('md');
const nodeTypes = useMemo(() => ({ table: TableNode }), []);
const edgeTypes = useMemo(() => ({ 'table-edge': TableEdge }), []);
const [isInitialLoadingNodes, setIsInitialLoadingNodes] = useState(true);
const [nodes, setNodes, onNodesChange] = useNodesState<TableNodeType>(
initialTables.map(tableToTableNode)
initialTables.map((table) => tableToTableNode(table, filteredSchemas))
);
const [edges, setEdges, onEdgesChange] =
useEdgesState<TableEdgeType>(initialEdges);
@@ -82,11 +91,13 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
}, [initialTables]);
useEffect(() => {
const initialNodes = initialTables.map(tableToTableNode);
const initialNodes = initialTables.map((table) =>
tableToTableNode(table, filteredSchemas)
);
if (equal(initialNodes, nodes)) {
setIsInitialLoadingNodes(false);
}
}, [initialTables, nodes]);
}, [initialTables, nodes, filteredSchemas]);
useEffect(() => {
if (!isInitialLoadingNodes) {
@@ -118,8 +129,10 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
}, [relationships, setEdges]);
useEffect(() => {
setNodes(tables.map(tableToTableNode));
}, [tables, setNodes]);
setNodes(
tables.map((table) => tableToTableNode(table, filteredSchemas))
);
}, [tables, setNodes, filteredSchemas]);
const onConnectHandler = useCallback(
async (params: AddEdgeParams) => {
@@ -18,20 +18,29 @@ import {
export interface RelationshipsSectionProps {}
export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
const { relationships } = useChartDB();
const { relationships, filteredSchemas } = useChartDB();
const [filterText, setFilterText] = React.useState('');
const { closeAllRelationshipsInSidebar } = useLayout();
const { t } = useTranslation();
const filteredRelationships = useMemo(() => {
const filter: (relationship: DBRelationship) => boolean = (
const filterName: (relationship: DBRelationship) => boolean = (
relationship
) =>
!filterText?.trim?.() ||
relationship.name.toLowerCase().includes(filterText.toLowerCase());
return relationships.filter(filter);
}, [relationships, filterText]);
const filterSchema: (relationship: DBRelationship) => boolean = (
relationship
) =>
!filteredSchemas ||
!relationship.sourceSchema ||
!relationship.targetSchema ||
(filteredSchemas.includes(relationship.sourceSchema) &&
filteredSchemas.includes(relationship.targetSchema));
return relationships.filter(filterSchema).filter(filterName);
}, [relationships, filterText, filteredSchemas]);
return (
<section className="flex flex-1 flex-col overflow-hidden px-2">
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import {
Select,
SelectContent,
@@ -12,14 +12,65 @@ import { RelationshipsSection } from './relationships-section/relationships-sect
import { useLayout } from '@/hooks/use-layout';
import { SidebarSection } from '@/context/layout-context/layout-context';
import { useTranslation } from 'react-i18next';
import { SelectBox, SelectBoxOption } from '@/components/select-box/select-box';
import { useChartDB } from '@/hooks/use-chartdb';
export interface SidePanelProps {}
export const SidePanel: React.FC<SidePanelProps> = () => {
const { t } = useTranslation();
const { schemas, filterSchemas, filteredSchemas } = useChartDB();
const { selectSidebarSection, selectedSidebarSection } = useLayout();
const schemasOptions: SelectBoxOption[] = useMemo(
() =>
schemas.map(
(schema): SelectBoxOption => ({
label: schema.name,
value: schema.id,
description: `(${schema.tableCount} tables)`,
})
),
[schemas]
);
const deselectAllSchemas = () => {
filterSchemas([]);
};
const selectAllSchemas = () => {
filterSchemas(schemas.map((schema) => schema.id));
};
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">
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[]);
}}
onSelectAll={selectAllSchemas}
onDeselectAll={deselectAllSchemas}
placeholder="Filter by schema"
inputPlaceholder="Search schema"
emptyPlaceholder="No schema found."
multiple
/>
</div>
</div>
)}
<div className="flex justify-center border-b pt-0.5">
<Select
value={selectedSidebarSection}
@@ -19,18 +19,23 @@ import {
export interface TablesSectionProps {}
export const TablesSection: React.FC<TablesSectionProps> = () => {
const { createTable, tables } = useChartDB();
const { createTable, tables, filteredSchemas } = useChartDB();
const { t } = useTranslation();
const { closeAllTablesInSidebar } = useLayout();
const [filterText, setFilterText] = React.useState('');
const filteredTables = useMemo(() => {
const filter: (table: DBTable) => boolean = (table) =>
const filterTableName: (table: DBTable) => boolean = (table) =>
!filterText?.trim?.() ||
table.name.toLowerCase().includes(filterText.toLowerCase());
return tables.filter(filter);
}, [tables, filterText]);
const filterSchema: (table: DBTable) => boolean = (table) =>
!filteredSchemas ||
!table.schema ||
filteredSchemas.includes(table.schema);
return tables.filter(filterSchema).filter(filterTableName);
}, [tables, filterText, filteredSchemas]);
return (
<section
@@ -45,7 +45,7 @@ import { useLayout } from '@/hooks/use-layout';
import { useTheme } from '@/hooks/use-theme';
import { enMetadata } from '@/i18n/locales/en';
import { esMetadata } from '@/i18n/locales/es';
import { useScrollAction } from '@/hooks/use-scroll-action';
import { useLocalConfig } from '@/hooks/use-local-config';
export interface TopNavbarProps {}
@@ -66,7 +66,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
} = useDialog();
const { setTheme, theme } = useTheme();
const { hideSidePanel, isSidePanelShowed, showSidePanel } = useLayout();
const { scrollAction, setScrollAction } = useScrollAction();
const { scrollAction, setScrollAction } = useLocalConfig();
const { effectiveTheme } = useTheme();
const { t, i18n } = useTranslation();
const { redo, undo, hasRedo, hasUndo } = useHistory();
+24 -22
View File
@@ -5,7 +5,6 @@ import { EditorPage } from './pages/editor-page/editor-page';
import { ChartDBProvider } from './context/chartdb-context/chartdb-provider';
import { ReactFlowProvider } from '@xyflow/react';
import { StorageProvider } from './context/storage-context/storage-provider';
import { ScrollProvider } from './context/scroll-context/scroll-provider';
import { ConfigProvider } from './context/config-context/config-provider';
import { HistoryProvider } from './context/history-context/history-provider';
import { RedoUndoStackProvider } from './context/history-context/redo-undo-stack-provider';
@@ -16,6 +15,7 @@ import { FullScreenLoaderProvider } from './context/full-screen-spinner-context/
import { ExamplesPage } from './pages/examples-page/examples-page';
import { KeyboardShortcutsProvider } from './context/keyboard-shortcuts-context/keyboard-shortcuts-provider';
import { ThemeProvider } from './context/theme-context/theme-provider';
import { LocalConfigProvider } from './context/local-config-context/local-config-provider';
const routes: RouteObject[] = [
...['', 'diagrams/:diagramId'].map((path) => ({
@@ -23,14 +23,14 @@ const routes: RouteObject[] = [
element: (
<FullScreenLoaderProvider>
<LayoutProvider>
<StorageProvider>
<ConfigProvider>
<RedoUndoStackProvider>
<ChartDBProvider>
<HistoryProvider>
<ThemeProvider>
<DialogProvider>
<ScrollProvider>
<LocalConfigProvider>
<StorageProvider>
<ConfigProvider>
<RedoUndoStackProvider>
<ChartDBProvider>
<HistoryProvider>
<ThemeProvider>
<DialogProvider>
<ReactFlowProvider>
<ExportImageProvider>
<KeyboardShortcutsProvider>
@@ -38,14 +38,14 @@ const routes: RouteObject[] = [
</KeyboardShortcutsProvider>
</ExportImageProvider>
</ReactFlowProvider>
</ScrollProvider>
</DialogProvider>
</ThemeProvider>
</HistoryProvider>
</ChartDBProvider>
</RedoUndoStackProvider>
</ConfigProvider>
</StorageProvider>
</DialogProvider>
</ThemeProvider>
</HistoryProvider>
</ChartDBProvider>
</RedoUndoStackProvider>
</ConfigProvider>
</StorageProvider>
</LocalConfigProvider>
</LayoutProvider>
</FullScreenLoaderProvider>
),
@@ -53,11 +53,13 @@ const routes: RouteObject[] = [
{
path: 'examples',
element: (
<StorageProvider>
<ThemeProvider>
<ExamplesPage />
</ThemeProvider>
</StorageProvider>
<LocalConfigProvider>
<StorageProvider>
<ThemeProvider>
<ExamplesPage />
</ThemeProvider>
</StorageProvider>
</LocalConfigProvider>
),
},
{