Files
chartdb/src/pages/editor-page/top-navbar/menu/menu.tsx
Jonathan Fishner e6783a89cc refactor: remove dedicated DBML import dialog (#907)
* refactor: remove dedicated DBML import dialog and unify import flow through ImportDatabaseDialog

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-09-15 19:24:35 +03:00

535 lines
23 KiB
TypeScript

import React, { useCallback } from 'react';
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from '@/components/menubar/menubar';
import { useChartDB } from '@/hooks/use-chartdb';
import { useDialog } from '@/hooks/use-dialog';
import { useExportImage } from '@/hooks/use-export-image';
import { databaseTypeToLabelMap } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type';
import {
KeyboardShortcutAction,
keyboardShortcutsForOS,
} from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
import { useHistory } from '@/hooks/use-history';
import { useTranslation } from 'react-i18next';
import { useLayout } from '@/hooks/use-layout';
import { useTheme } from '@/hooks/use-theme';
import { useLocalConfig } from '@/hooks/use-local-config';
import { useNavigate } from 'react-router-dom';
import { useAlert } from '@/context/alert-context/alert-context';
export interface MenuProps {}
export const Menu: React.FC<MenuProps> = () => {
const {
clearDiagramData,
deleteDiagram,
updateDiagramUpdatedAt,
databaseType,
} = useChartDB();
const {
openCreateDiagramDialog,
openOpenDiagramDialog,
openExportSQLDialog,
openImportDatabaseDialog,
openExportImageDialog,
openExportDiagramDialog,
openImportDiagramDialog,
} = useDialog();
const { showAlert } = useAlert();
const { setTheme, theme } = useTheme();
const { hideSidePanel, isSidePanelShowed, showSidePanel } = useLayout();
const {
scrollAction,
setScrollAction,
setShowCardinality,
showCardinality,
setShowFieldAttributes,
showFieldAttributes,
setShowMiniMapOnCanvas,
showMiniMapOnCanvas,
showDBViews,
setShowDBViews,
} = useLocalConfig();
const { t } = useTranslation();
const { redo, undo, hasRedo, hasUndo } = useHistory();
const { exportImage } = useExportImage();
const navigate = useNavigate();
const handleDeleteDiagramAction = useCallback(() => {
deleteDiagram();
navigate('/');
}, [deleteDiagram, navigate]);
const createNewDiagram = () => {
openCreateDiagramDialog();
};
const openDiagram = () => {
openOpenDiagramDialog();
};
const exportSVG = useCallback(() => {
exportImage('svg', {
scale: 1,
transparent: true,
includePatternBG: false,
});
}, [exportImage]);
const exportPNG = useCallback(() => {
openExportImageDialog({
format: 'png',
});
}, [openExportImageDialog]);
const exportJPG = useCallback(() => {
openExportImageDialog({
format: 'jpeg',
});
}, [openExportImageDialog]);
const openChartDBDocs = useCallback(() => {
window.open('https://docs.chartdb.io', '_blank');
}, []);
const openJoinDiscord = useCallback(() => {
window.open('https://discord.gg/QeFwyWSKwC', '_blank');
}, []);
const exportSQL = useCallback(
(databaseType: DatabaseType) => {
if (databaseType === DatabaseType.GENERIC) {
openExportSQLDialog({
targetDatabaseType: DatabaseType.GENERIC,
});
return;
}
openExportSQLDialog({
targetDatabaseType: databaseType,
});
},
[openExportSQLDialog]
);
const showOrHideSidePanel = useCallback(() => {
if (isSidePanelShowed) {
hideSidePanel();
} else {
showSidePanel();
}
}, [isSidePanelShowed, showSidePanel, hideSidePanel]);
const showOrHideCardinality = useCallback(() => {
setShowCardinality(!showCardinality);
}, [showCardinality, setShowCardinality]);
const showOrHideFieldAttributes = useCallback(() => {
setShowFieldAttributes(!showFieldAttributes);
}, [showFieldAttributes, setShowFieldAttributes]);
const showOrHideMiniMap = useCallback(() => {
setShowMiniMapOnCanvas(!showMiniMapOnCanvas);
}, [showMiniMapOnCanvas, setShowMiniMapOnCanvas]);
const emojiAI = '✨';
return (
<Menubar className="h-8 border-none py-2 shadow-none md:h-10 md:py-0">
<MenubarMenu>
<MenubarTrigger>{t('menu.actions.actions')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={createNewDiagram}>
{t('menu.actions.new')}
</MenubarItem>
<MenubarItem onClick={openDiagram}>
{t('menu.actions.browse')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.OPEN_DIAGRAM
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarItem onClick={updateDiagramUpdatedAt}>
{t('menu.actions.save')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.SAVE_DIAGRAM
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.actions.import')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem onClick={openImportDiagramDialog}>
.json
</MenubarItem>
<MenubarSeparator />
<MenubarItem
onClick={() =>
openImportDatabaseDialog({
databaseType: DatabaseType.POSTGRESQL,
})
}
>
{databaseTypeToLabelMap['postgresql']}
</MenubarItem>
<MenubarItem
onClick={() =>
openImportDatabaseDialog({
databaseType: DatabaseType.MYSQL,
})
}
>
{databaseTypeToLabelMap['mysql']}
</MenubarItem>
<MenubarItem
onClick={() =>
openImportDatabaseDialog({
databaseType: DatabaseType.SQL_SERVER,
})
}
>
{databaseTypeToLabelMap['sql_server']}
</MenubarItem>
<MenubarItem
onClick={() =>
openImportDatabaseDialog({
databaseType: DatabaseType.MARIADB,
})
}
>
{databaseTypeToLabelMap['mariadb']}
</MenubarItem>
<MenubarItem
onClick={() =>
openImportDatabaseDialog({
databaseType: DatabaseType.SQLITE,
})
}
>
{databaseTypeToLabelMap['sqlite']}
</MenubarItem>
<MenubarItem
onClick={() =>
openImportDatabaseDialog({
databaseType: DatabaseType.ORACLE,
})
}
>
{databaseTypeToLabelMap['oracle']}
</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.actions.export_sql')}
</MenubarSubTrigger>
<MenubarSubContent>
{databaseType === DatabaseType.GENERIC ? (
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.GENERIC)
}
>
{databaseTypeToLabelMap['generic']}
</MenubarItem>
) : null}
{databaseType !== DatabaseType.GENERIC ? (
<MenubarItem
onClick={() => exportSQL(databaseType)}
>
{databaseTypeToLabelMap[databaseType]}
</MenubarItem>
) : null}
{databaseType !== DatabaseType.POSTGRESQL ? (
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.POSTGRESQL)
}
>
{databaseTypeToLabelMap['postgresql']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
) : null}
{databaseType !== DatabaseType.MYSQL ? (
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.MYSQL)
}
>
{databaseTypeToLabelMap['mysql']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
) : null}
{databaseType !== DatabaseType.SQL_SERVER ? (
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.SQL_SERVER)
}
>
{databaseTypeToLabelMap['sql_server']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
) : null}
{databaseType !== DatabaseType.MARIADB ? (
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.MARIADB)
}
>
{databaseTypeToLabelMap['mariadb']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
) : null}
{databaseType !== DatabaseType.SQLITE ? (
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.SQLITE)
}
>
{databaseTypeToLabelMap['sqlite']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
) : null}
</MenubarSubContent>
</MenubarSub>
<MenubarSub>
<MenubarSubTrigger>
{t('menu.actions.export_as')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem onClick={exportPNG}>PNG</MenubarItem>
<MenubarItem onClick={exportJPG}>JPG</MenubarItem>
<MenubarItem onClick={exportSVG}>SVG</MenubarItem>
<MenubarSeparator />
<MenubarItem onClick={openExportDiagramDialog}>
JSON
</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem
onClick={() =>
showAlert({
title: t('delete_diagram_alert.title'),
description: t(
'delete_diagram_alert.description'
),
actionLabel: t('delete_diagram_alert.delete'),
closeLabel: t('delete_diagram_alert.cancel'),
onAction: handleDeleteDiagramAction,
})
}
>
{t('menu.actions.delete_diagram')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{t('menu.edit.edit')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={undo} disabled={!hasUndo}>
{t('menu.edit.undo')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.UNDO
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarItem onClick={redo} disabled={!hasRedo}>
{t('menu.edit.redo')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.REDO
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem
onClick={() =>
showAlert({
title: t('clear_diagram_alert.title'),
description: t(
'clear_diagram_alert.description'
),
actionLabel: t('clear_diagram_alert.clear'),
closeLabel: t('clear_diagram_alert.cancel'),
onAction: clearDiagramData,
})
}
>
{t('menu.edit.clear')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{t('menu.view.view')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={showOrHideSidePanel}>
{isSidePanelShowed
? t('menu.view.hide_sidebar')
: t('menu.view.show_sidebar')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.TOGGLE_SIDE_PANEL
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem onClick={showOrHideCardinality}>
{showCardinality
? t('menu.view.hide_cardinality')
: t('menu.view.show_cardinality')}
</MenubarItem>
<MenubarItem onClick={showOrHideFieldAttributes}>
{showFieldAttributes
? t('menu.view.hide_field_attributes')
: t('menu.view.show_field_attributes')}
</MenubarItem>
<MenubarItem onClick={showOrHideMiniMap}>
{showMiniMapOnCanvas
? t('menu.view.hide_minimap')
: t('menu.view.show_minimap')}
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.view.zoom_on_scroll')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarCheckboxItem
checked={scrollAction === 'zoom'}
onClick={() => setScrollAction('zoom')}
>
{t('zoom.on')}
</MenubarCheckboxItem>
<MenubarCheckboxItem
checked={scrollAction === 'pan'}
onClick={() => setScrollAction('pan')}
>
{t('zoom.off')}
</MenubarCheckboxItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.view.show_views')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarCheckboxItem
checked={showDBViews}
onClick={() => setShowDBViews(true)}
>
{t('on')}
</MenubarCheckboxItem>
<MenubarCheckboxItem
checked={!showDBViews}
onClick={() => setShowDBViews(false)}
>
{t('off')}
</MenubarCheckboxItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger className="flex items-center gap-1">
<span>{t('menu.view.theme')}</span>
<div className="flex-1" />
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.TOGGLE_THEME
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarCheckboxItem
checked={theme === 'system'}
onClick={() => setTheme('system')}
>
{t('theme.system')}
</MenubarCheckboxItem>
<MenubarCheckboxItem
checked={theme === 'light'}
onClick={() => setTheme('light')}
>
{t('theme.light')}
</MenubarCheckboxItem>
<MenubarCheckboxItem
checked={theme === 'dark'}
onClick={() => setTheme('dark')}
>
{t('theme.dark')}
</MenubarCheckboxItem>
</MenubarSubContent>
</MenubarSub>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{t('menu.backup.backup')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={openExportDiagramDialog}>
{t('menu.backup.export_diagram')}
</MenubarItem>
<MenubarItem onClick={openImportDiagramDialog}>
{t('menu.backup.restore_diagram')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{t('menu.help.help')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={openChartDBDocs}>
{t('menu.help.docs_website')}
</MenubarItem>
<MenubarItem onClick={openJoinDiscord}>
{t('menu.help.join_discord')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
);
};