multi schemas alert

This commit is contained in:
Guy Ben-Aharon
2024-09-19 23:45:22 +03:00
committed by Guy Ben-Aharon
parent 2f2828bc36
commit 420733be8e
12 changed files with 198 additions and 19 deletions

View File

@@ -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(

View File

@@ -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>
);

View File

@@ -11,6 +11,7 @@ type ToasterToast = ToastProps & {
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
layout?: 'row' | 'column';
};
const actionTypes = {

View File

@@ -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(
() => ({

View File

@@ -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,
});

View File

@@ -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}

View File

@@ -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,
});

View File

@@ -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}

View File

@@ -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',

View File

@@ -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',

View File

@@ -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

View File

@@ -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>