mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-19 22:19:57 +00:00
multi schemas alert
This commit is contained in:
committed by
Guy Ben-Aharon
parent
2f2828bc36
commit
420733be8e
@@ -18,6 +18,7 @@ import {
|
||||
} from '@/components/popover/popover';
|
||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export interface SelectBoxOption {
|
||||
value: string;
|
||||
@@ -41,6 +42,8 @@ interface SelectBoxProps {
|
||||
showClear?: boolean;
|
||||
keepOrder?: boolean;
|
||||
disabled?: boolean;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
@@ -61,13 +64,27 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
showClear,
|
||||
keepOrder,
|
||||
disabled,
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [searchTerm, setSearchTerm] = React.useState<string>('');
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [isOpen, setIsOpen] = React.useState(open ?? false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(open ?? false);
|
||||
}, [open]);
|
||||
|
||||
const onOpenChange = React.useCallback(
|
||||
(isOpen: boolean) => {
|
||||
setOpen?.(isOpen);
|
||||
setIsOpen(isOpen);
|
||||
},
|
||||
[setOpen]
|
||||
);
|
||||
|
||||
const handleSelect = React.useCallback(
|
||||
(selectedValue: string) => {
|
||||
if (multiple) {
|
||||
@@ -140,7 +157,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen} modal={true}>
|
||||
<Popover open={isOpen} onOpenChange={onOpenChange} modal={true}>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@@ -19,19 +19,25 @@ export function Toaster() {
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
layout = 'row',
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
<div
|
||||
className={`grid gap-1${layout === 'column' ? ' w-full' : ''}`}
|
||||
>
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && (
|
||||
<ToastDescription>
|
||||
{description}
|
||||
</ToastDescription>
|
||||
)}
|
||||
{layout === 'column' ? (
|
||||
<div className="mt-2">{action}</div>
|
||||
) : null}
|
||||
</div>
|
||||
{action}
|
||||
{layout === 'row' ? action : null}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ type ToasterToast = ToastProps & {
|
||||
title?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
action?: ToastActionElement;
|
||||
layout?: 'row' | 'column';
|
||||
};
|
||||
|
||||
const actionTypes = {
|
||||
|
||||
@@ -86,16 +86,23 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[diagramId, setSchemasFilter]
|
||||
);
|
||||
|
||||
const filteredSchemas: ChartDBContext['filteredSchemas'] = useMemo(
|
||||
() =>
|
||||
schemas.length > 0
|
||||
? (schemasFilter[diagramId] ?? [
|
||||
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,
|
||||
])
|
||||
: undefined,
|
||||
[schemasFilter, diagramId, schemas, defaultSchemaName]
|
||||
]
|
||||
);
|
||||
}, [schemasFilter, diagramId, schemas, defaultSchemaName]);
|
||||
|
||||
const currentDiagram: Diagram = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -18,6 +18,10 @@ export interface LayoutContext {
|
||||
isSidePanelShowed: boolean;
|
||||
hideSidePanel: () => void;
|
||||
showSidePanel: () => void;
|
||||
|
||||
isSelectSchemaOpen: boolean;
|
||||
openSelectSchema: () => void;
|
||||
closeSelectSchema: () => void;
|
||||
}
|
||||
|
||||
export const layoutContext = createContext<LayoutContext>({
|
||||
@@ -35,4 +39,8 @@ export const layoutContext = createContext<LayoutContext>({
|
||||
isSidePanelShowed: false,
|
||||
hideSidePanel: emptyFn,
|
||||
showSidePanel: emptyFn,
|
||||
|
||||
isSelectSchemaOpen: false,
|
||||
openSelectSchema: emptyFn,
|
||||
closeSelectSchema: emptyFn,
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ 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('');
|
||||
@@ -42,6 +44,12 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setSelectedSidebarSection('relationships');
|
||||
setOpenedRelationshipInSidebar(relationshipId);
|
||||
};
|
||||
|
||||
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
|
||||
setIsSelectSchemaOpen(true);
|
||||
|
||||
const closeSelectSchema: LayoutContext['closeSelectSchema'] = () =>
|
||||
setIsSelectSchemaOpen(false);
|
||||
return (
|
||||
<layoutContext.Provider
|
||||
value={{
|
||||
@@ -56,6 +64,9 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
isSidePanelShowed,
|
||||
hideSidePanel,
|
||||
showSidePanel,
|
||||
isSelectSchemaOpen,
|
||||
openSelectSchema,
|
||||
closeSelectSchema,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -18,6 +18,11 @@ export interface LocalConfigContext {
|
||||
|
||||
showCardinality: boolean;
|
||||
setShowCardinality: (showCardinality: boolean) => void;
|
||||
|
||||
hideMultiSchemaNotification: boolean;
|
||||
setHideMultiSchemaNotification: (
|
||||
hideMultiSchemaNotification: boolean
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const LocalConfigContext = createContext<LocalConfigContext>({
|
||||
@@ -32,4 +37,7 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
||||
|
||||
showCardinality: false,
|
||||
setShowCardinality: emptyFn,
|
||||
|
||||
hideMultiSchemaNotification: false,
|
||||
setHideMultiSchemaNotification: emptyFn,
|
||||
});
|
||||
|
||||
@@ -32,6 +32,19 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
(localStorage.getItem(showCardinalityKey) || 'false') === 'true'
|
||||
);
|
||||
|
||||
const [hideMultiSchemaNotification, setHideMultiSchemaNotification] =
|
||||
React.useState<boolean>(
|
||||
(localStorage.getItem('hide_multi_schema_notification') ||
|
||||
'false') === 'true'
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(
|
||||
'hide_multi_schema_notification',
|
||||
hideMultiSchemaNotification.toString()
|
||||
);
|
||||
}, [hideMultiSchemaNotification]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(themeKey, theme);
|
||||
}, [theme]);
|
||||
@@ -59,6 +72,8 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setSchemasFilter,
|
||||
showCardinality,
|
||||
setShowCardinality,
|
||||
hideMultiSchemaNotification,
|
||||
setHideMultiSchemaNotification,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -62,6 +62,15 @@ export const en = {
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'Multiple Schemas',
|
||||
description:
|
||||
'{{schemasCount}} schemas on this diagram, currently showing: {{formattedSchemas}}.',
|
||||
dont_show_again: "Don't show again",
|
||||
change_schema: 'Change',
|
||||
none: 'none',
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'System',
|
||||
light: 'Light',
|
||||
|
||||
@@ -261,6 +261,15 @@ export const es: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'Múltiples Esquemas',
|
||||
description:
|
||||
'{{schemasCount}} esquemas en este diagrama, Actualmente mostrando: {{formattedSchemas}}.',
|
||||
dont_show_again: 'No mostrar de nuevo',
|
||||
change_schema: 'Cambiar',
|
||||
none: 'nada',
|
||||
},
|
||||
|
||||
relationship_type: {
|
||||
one_to_one: 'Uno a Uno',
|
||||
one_to_many: 'Uno a Muchos',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { TopNavbar } from './top-navbar/top-navbar';
|
||||
import {
|
||||
ResizableHandle,
|
||||
@@ -16,6 +16,7 @@ import { Toaster } from '@/components/toast/toaster';
|
||||
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import { useToast } from '@/components/toast/use-toast';
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
@@ -25,10 +26,14 @@ import {
|
||||
} from '@/components/drawer/drawer';
|
||||
import { Separator } from '@/components/separator/separator';
|
||||
import { Diagram } from '@/lib/domain/diagram';
|
||||
import { ToastAction } from '@/components/toast/toast';
|
||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const EditorPage: React.FC = () => {
|
||||
const { loadDiagram, currentDiagram } = useChartDB();
|
||||
const { isSidePanelShowed, hideSidePanel } = useLayout();
|
||||
const { loadDiagram, currentDiagram, schemas, filteredSchemas } =
|
||||
useChartDB();
|
||||
const { isSidePanelShowed, hideSidePanel, openSelectSchema } = useLayout();
|
||||
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
||||
const { showLoader, hideLoader } = useFullScreenLoader();
|
||||
const { openCreateDiagramDialog } = useDialog();
|
||||
@@ -39,6 +44,10 @@ export const EditorPage: React.FC = () => {
|
||||
const { isXl } = useBreakpoint('xl');
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
const [initialDiagram, setInitialDiagram] = useState<Diagram | undefined>();
|
||||
const { hideMultiSchemaNotification, setHideMultiSchemaNotification } =
|
||||
useLocalConfig();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!config) {
|
||||
@@ -90,6 +99,66 @@ export const EditorPage: React.FC = () => {
|
||||
updateConfig,
|
||||
]);
|
||||
|
||||
const lastDiagramId = useRef<string>('');
|
||||
|
||||
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: 5500,
|
||||
title: t('multiple_schemas_alert.title'),
|
||||
description: t('multiple_schemas_alert.description', {
|
||||
schemasCount: schemas.length,
|
||||
formattedSchemas,
|
||||
}),
|
||||
variant: 'default',
|
||||
layout: 'column',
|
||||
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">
|
||||
<ToastAction
|
||||
altText="Don't show this notification again"
|
||||
className="flex-nowrap"
|
||||
onClick={() => setHideMultiSchemaNotification(true)}
|
||||
>
|
||||
{t('multiple_schemas_alert.dont_show_again')}
|
||||
</ToastAction>
|
||||
<ToastAction
|
||||
onClick={() => openSelectSchema()}
|
||||
altText="Change the schema"
|
||||
className="border border-pink-600 bg-pink-600 text-white hover:bg-pink-500"
|
||||
>
|
||||
{t('multiple_schemas_alert.change_schema')}
|
||||
</ToastAction>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
}, [
|
||||
schemas,
|
||||
filteredSchemas,
|
||||
toast,
|
||||
currentDiagram.id,
|
||||
diagramId,
|
||||
openSelectSchema,
|
||||
t,
|
||||
hideMultiSchemaNotification,
|
||||
setHideMultiSchemaNotification,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -20,7 +20,13 @@ export interface SidePanelProps {}
|
||||
export const SidePanel: React.FC<SidePanelProps> = () => {
|
||||
const { t } = useTranslation();
|
||||
const { schemas, filterSchemas, filteredSchemas } = useChartDB();
|
||||
const { selectSidebarSection, selectedSidebarSection } = useLayout();
|
||||
const {
|
||||
selectSidebarSection,
|
||||
selectedSidebarSection,
|
||||
isSelectSchemaOpen,
|
||||
openSelectSchema,
|
||||
closeSelectSchema,
|
||||
} = useLayout();
|
||||
|
||||
const schemasOptions: SelectBoxOption[] = useMemo(
|
||||
() =>
|
||||
@@ -34,6 +40,17 @@ export const SidePanel: React.FC<SidePanelProps> = () => {
|
||||
[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 && (
|
||||
@@ -56,6 +73,8 @@ export const SidePanel: React.FC<SidePanelProps> = () => {
|
||||
inputPlaceholder={t('side_panel.search_schema')}
|
||||
emptyPlaceholder={t('side_panel.no_schemas_found')}
|
||||
multiple
|
||||
open={isSelectSchemaOpen}
|
||||
onOpenChange={setIsSelectSchemaOpen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user