mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-03 05:23:26 +00:00
293 lines
11 KiB
TypeScript
293 lines
11 KiB
TypeScript
import React, {
|
|
Suspense,
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
useState,
|
|
} from 'react';
|
|
import { TopNavbar } from './top-navbar/top-navbar';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import { useConfig } from '@/hooks/use-config';
|
|
import { useChartDB } from '@/hooks/use-chartdb';
|
|
import { useDialog } from '@/hooks/use-dialog';
|
|
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
|
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 type { Diagram } from '@/lib/domain/diagram';
|
|
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';
|
|
import { StorageProvider } from '@/context/storage-context/storage-provider';
|
|
import { ConfigProvider } from '@/context/config-context/config-provider';
|
|
import { RedoUndoStackProvider } from '@/context/history-context/redo-undo-stack-provider';
|
|
import { ChartDBProvider } from '@/context/chartdb-context/chartdb-provider';
|
|
import { HistoryProvider } from '@/context/history-context/history-provider';
|
|
import { ThemeProvider } from '@/context/theme-context/theme-provider';
|
|
import { ReactFlowProvider } from '@xyflow/react';
|
|
import { ExportImageProvider } from '@/context/export-image-context/export-image-provider';
|
|
import { DialogProvider } from '@/context/dialog-context/dialog-provider';
|
|
import { KeyboardShortcutsProvider } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts-provider';
|
|
import { Spinner } from '@/components/spinner/spinner';
|
|
import { Helmet } from 'react-helmet-async';
|
|
import { useStorage } from '@/hooks/use-storage';
|
|
|
|
const OPEN_STAR_US_AFTER_SECONDS = 30;
|
|
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
|
|
|
export const EditorDesktopLayoutLazy = React.lazy(
|
|
() => import('./editor-desktop-layout')
|
|
);
|
|
|
|
export const EditorMobileLayoutLazy = React.lazy(
|
|
() => import('./editor-mobile-layout')
|
|
);
|
|
|
|
const EditorPageComponent: React.FC = () => {
|
|
const {
|
|
loadDiagram,
|
|
diagramName,
|
|
currentDiagram,
|
|
schemas,
|
|
filteredSchemas,
|
|
} = useChartDB();
|
|
const { openSelectSchema, showSidePanel } = useLayout();
|
|
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
|
const { showLoader, hideLoader } = useFullScreenLoader();
|
|
const { openCreateDiagramDialog, openStarUsDialog } = useDialog();
|
|
const { diagramId } = useParams<{ diagramId: string }>();
|
|
const { config, updateConfig } = useConfig();
|
|
const navigate = useNavigate();
|
|
const { isMd: isDesktop } = useBreakpoint('md');
|
|
const [initialDiagram, setInitialDiagram] = useState<Diagram | undefined>();
|
|
const {
|
|
hideMultiSchemaNotification,
|
|
setHideMultiSchemaNotification,
|
|
starUsDialogLastOpen,
|
|
setStarUsDialogLastOpen,
|
|
githubRepoOpened,
|
|
} = useLocalConfig();
|
|
const { toast } = useToast();
|
|
const { t } = useTranslation();
|
|
const { listDiagrams } = useStorage();
|
|
|
|
useEffect(() => {
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
if (currentDiagram?.id === diagramId) {
|
|
return;
|
|
}
|
|
|
|
const loadDefaultDiagram = async () => {
|
|
if (diagramId) {
|
|
setInitialDiagram(undefined);
|
|
showLoader();
|
|
resetRedoStack();
|
|
resetUndoStack();
|
|
const diagram = await loadDiagram(diagramId);
|
|
if (!diagram) {
|
|
navigate('/');
|
|
}
|
|
setInitialDiagram(diagram);
|
|
hideLoader();
|
|
} else if (!diagramId && config.defaultDiagramId) {
|
|
const diagram = await loadDiagram(config.defaultDiagramId);
|
|
if (!diagram) {
|
|
await updateConfig({
|
|
defaultDiagramId: '',
|
|
});
|
|
navigate('/');
|
|
} else {
|
|
navigate(`/diagrams/${config.defaultDiagramId}`);
|
|
}
|
|
} else {
|
|
const diagrams = await listDiagrams();
|
|
|
|
if (diagrams.length > 0) {
|
|
const defaultDiagramId = diagrams[0].id;
|
|
await updateConfig({ defaultDiagramId });
|
|
navigate(`/diagrams/${defaultDiagramId}`);
|
|
} else {
|
|
openCreateDiagramDialog();
|
|
}
|
|
}
|
|
};
|
|
loadDefaultDiagram();
|
|
}, [
|
|
diagramId,
|
|
openCreateDiagramDialog,
|
|
config,
|
|
navigate,
|
|
listDiagrams,
|
|
loadDiagram,
|
|
resetRedoStack,
|
|
resetUndoStack,
|
|
hideLoader,
|
|
showLoader,
|
|
currentDiagram?.id,
|
|
updateConfig,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (!currentDiagram?.id || githubRepoOpened) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
new Date().getTime() - starUsDialogLastOpen >
|
|
1000 * 60 * 60 * 24 * SHOW_STAR_US_AGAIN_AFTER_DAYS
|
|
) {
|
|
const lastOpen = new Date().getTime();
|
|
setStarUsDialogLastOpen(lastOpen);
|
|
setTimeout(openStarUsDialog, OPEN_STAR_US_AFTER_SECONDS * 1000);
|
|
}
|
|
}, [
|
|
currentDiagram?.id,
|
|
githubRepoOpened,
|
|
openStarUsDialog,
|
|
setStarUsDialogLastOpen,
|
|
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: 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={() => handleChangeSchema()}
|
|
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,
|
|
handleChangeSchema,
|
|
hideMultiSchemaNotification,
|
|
setHideMultiSchemaNotification,
|
|
]);
|
|
|
|
return (
|
|
<>
|
|
<Helmet>
|
|
<title>
|
|
{diagramName
|
|
? `ChartDB - ${diagramName} Diagram | Visualize Database Schemas`
|
|
: 'ChartDB - Create & Visualize Database Schema Diagrams'}
|
|
</title>
|
|
</Helmet>
|
|
<section
|
|
className={`bg-background ${isDesktop ? 'h-screen w-screen' : 'h-dvh w-dvw'} flex select-none flex-col overflow-x-hidden`}
|
|
>
|
|
<TopNavbar />
|
|
<Suspense
|
|
fallback={
|
|
<div className="flex flex-1 items-center justify-center">
|
|
<Spinner size={isDesktop ? 'large' : 'medium'} />
|
|
</div>
|
|
}
|
|
>
|
|
{isDesktop ? (
|
|
<EditorDesktopLayoutLazy
|
|
initialDiagram={initialDiagram}
|
|
/>
|
|
) : (
|
|
<EditorMobileLayoutLazy
|
|
initialDiagram={initialDiagram}
|
|
/>
|
|
)}
|
|
</Suspense>
|
|
</section>
|
|
<Toaster />
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const EditorPage: React.FC = () => (
|
|
<LocalConfigProvider>
|
|
<ThemeProvider>
|
|
<FullScreenLoaderProvider>
|
|
<LayoutProvider>
|
|
<StorageProvider>
|
|
<ConfigProvider>
|
|
<RedoUndoStackProvider>
|
|
<ChartDBProvider>
|
|
<HistoryProvider>
|
|
<ReactFlowProvider>
|
|
<ExportImageProvider>
|
|
<DialogProvider>
|
|
<KeyboardShortcutsProvider>
|
|
<EditorPageComponent />
|
|
</KeyboardShortcutsProvider>
|
|
</DialogProvider>
|
|
</ExportImageProvider>
|
|
</ReactFlowProvider>
|
|
</HistoryProvider>
|
|
</ChartDBProvider>
|
|
</RedoUndoStackProvider>
|
|
</ConfigProvider>
|
|
</StorageProvider>
|
|
</LayoutProvider>
|
|
</FullScreenLoaderProvider>
|
|
</ThemeProvider>
|
|
</LocalConfigProvider>
|
|
);
|