From 94bed7fcceb513a16008be5099f4b463daab4141 Mon Sep 17 00:00:00 2001 From: Guy Ben-Aharon Date: Thu, 2 Jan 2025 18:11:34 +0200 Subject: [PATCH] refactor: move menu to a separate component (#517) * refactor: move menu to a separate component * merge --- .../editor-page/top-navbar/menu/menu.tsx | 512 +++++++++++++++++ .../editor-page/top-navbar/top-navbar.tsx | 534 +----------------- 2 files changed, 514 insertions(+), 532 deletions(-) create mode 100644 src/pages/editor-page/top-navbar/menu/menu.tsx diff --git a/src/pages/editor-page/top-navbar/menu/menu.tsx b/src/pages/editor-page/top-navbar/menu/menu.tsx new file mode 100644 index 00000000..18196673 --- /dev/null +++ b/src/pages/editor-page/top-navbar/menu/menu.tsx @@ -0,0 +1,512 @@ +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 { useConfig } from '@/hooks/use-config'; +import { IS_CHARTDB_IO } from '@/lib/env'; +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 = () => { + 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, + setShowDependenciesOnCanvas, + showDependenciesOnCanvas, + setShowMiniMapOnCanvas, + showMiniMapOnCanvas, + } = useLocalConfig(); + const { t } = useTranslation(); + const { redo, undo, hasRedo, hasUndo } = useHistory(); + const { config, updateConfig } = useConfig(); + const { exportImage } = useExportImage(); + const navigate = useNavigate(); + + const handleDeleteDiagramAction = useCallback(() => { + deleteDiagram(); + navigate('/'); + }, [deleteDiagram, navigate]); + + const createNewDiagram = () => { + openCreateDiagramDialog(); + }; + + const openDiagram = () => { + openOpenDiagramDialog(); + }; + + const exportSVG = useCallback(() => { + exportImage('svg', 1); + }, [exportImage]); + + const exportPNG = useCallback(() => { + openExportImageDialog({ + format: 'png', + }); + }, [openExportImageDialog]); + + const exportJPG = useCallback(() => { + openExportImageDialog({ + format: 'jpeg', + }); + }, [openExportImageDialog]); + + const openChartDBIO = useCallback(() => { + window.location.href = 'https://chartdb.io'; + }, []); + + const openJoinDiscord = useCallback(() => { + window.open('https://discord.gg/QeFwyWSKwC', '_blank'); + }, []); + + const openCalendly = useCallback(() => { + window.open('https://calendly.com/fishner/15min', '_blank'); + }, []); + + const exportSQL = useCallback( + (databaseType: DatabaseType) => { + if (databaseType === DatabaseType.GENERIC) { + openExportSQLDialog({ + targetDatabaseType: DatabaseType.GENERIC, + }); + + return; + } + + if (IS_CHARTDB_IO) { + const now = new Date(); + const lastExportsInLastHalfHour = + config?.exportActions?.filter( + (date) => + now.getTime() - date.getTime() < 30 * 60 * 1000 + ) ?? []; + + if (lastExportsInLastHalfHour.length >= 5) { + showAlert({ + title: 'Export SQL Limit Reached', + content: ( +
+
+ We set a budget to allow the community to + check the feature. You have reached the + limit of 5 AI exports every 30min. +
+
+ Feel free to use your OPENAI_TOKEN, see the + manual{' '} + + here. + +
+
+ ), + closeLabel: 'Close', + }); + return; + } + + updateConfig({ + exportActions: [...lastExportsInLastHalfHour, now], + }); + } + + openExportSQLDialog({ + targetDatabaseType: databaseType, + }); + }, + [config?.exportActions, updateConfig, showAlert, openExportSQLDialog] + ); + + const showOrHideSidePanel = useCallback(() => { + if (isSidePanelShowed) { + hideSidePanel(); + } else { + showSidePanel(); + } + }, [isSidePanelShowed, showSidePanel, hideSidePanel]); + + const showOrHideCardinality = useCallback(() => { + setShowCardinality(!showCardinality); + }, [showCardinality, setShowCardinality]); + + const showOrHideDependencies = useCallback(() => { + setShowDependenciesOnCanvas(!showDependenciesOnCanvas); + }, [showDependenciesOnCanvas, setShowDependenciesOnCanvas]); + + const showOrHideMiniMap = useCallback(() => { + setShowMiniMapOnCanvas(!showMiniMapOnCanvas); + }, [showMiniMapOnCanvas, setShowMiniMapOnCanvas]); + + const emojiAI = '✨'; + + return ( + + + {t('menu.file.file')} + + + {t('menu.file.new')} + + + {t('menu.file.open')} + + { + keyboardShortcutsForOS[ + KeyboardShortcutAction.OPEN_DIAGRAM + ].keyCombinationLabel + } + + + + {t('menu.file.save')} + + { + keyboardShortcutsForOS[ + KeyboardShortcutAction.SAVE_DIAGRAM + ].keyCombinationLabel + } + + + + + + {t('menu.file.import_database')} + + + + openImportDatabaseDialog({ + databaseType: DatabaseType.POSTGRESQL, + }) + } + > + {databaseTypeToLabelMap['postgresql']} + + + openImportDatabaseDialog({ + databaseType: DatabaseType.MYSQL, + }) + } + > + {databaseTypeToLabelMap['mysql']} + + + openImportDatabaseDialog({ + databaseType: DatabaseType.SQL_SERVER, + }) + } + > + {databaseTypeToLabelMap['sql_server']} + + + openImportDatabaseDialog({ + databaseType: DatabaseType.MARIADB, + }) + } + > + {databaseTypeToLabelMap['mariadb']} + + + openImportDatabaseDialog({ + databaseType: DatabaseType.SQLITE, + }) + } + > + {databaseTypeToLabelMap['sqlite']} + + + + + + + {t('menu.file.export_sql')} + + + exportSQL(DatabaseType.GENERIC)} + > + {databaseTypeToLabelMap['generic']} + + + exportSQL(DatabaseType.POSTGRESQL) + } + > + {databaseTypeToLabelMap['postgresql']} + + {emojiAI} + + + exportSQL(DatabaseType.MYSQL)} + > + {databaseTypeToLabelMap['mysql']} + + {emojiAI} + + + + exportSQL(DatabaseType.SQL_SERVER) + } + > + {databaseTypeToLabelMap['sql_server']} + + {emojiAI} + + + exportSQL(DatabaseType.MARIADB)} + > + {databaseTypeToLabelMap['mariadb']} + + {emojiAI} + + + exportSQL(DatabaseType.SQLITE)} + > + {databaseTypeToLabelMap['sqlite']} + + {emojiAI} + + + + + + + {t('menu.file.export_as')} + + + PNG + JPG + SVG + + + + + 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.file.delete_diagram')} + + + {t('menu.file.exit')} + + + + {t('menu.edit.edit')} + + + {t('menu.edit.undo')} + + { + keyboardShortcutsForOS[ + KeyboardShortcutAction.UNDO + ].keyCombinationLabel + } + + + + {t('menu.edit.redo')} + + { + keyboardShortcutsForOS[ + KeyboardShortcutAction.REDO + ].keyCombinationLabel + } + + + + + 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')} + + + + + {t('menu.view.view')} + + + {isSidePanelShowed + ? t('menu.view.hide_sidebar') + : t('menu.view.show_sidebar')} + + { + keyboardShortcutsForOS[ + KeyboardShortcutAction.TOGGLE_SIDE_PANEL + ].keyCombinationLabel + } + + + + + {showCardinality + ? t('menu.view.hide_cardinality') + : t('menu.view.show_cardinality')} + + {databaseType !== DatabaseType.CLICKHOUSE ? ( + + {showDependenciesOnCanvas + ? t('menu.view.hide_dependencies') + : t('menu.view.show_dependencies')} + + ) : null} + + {showMiniMapOnCanvas + ? t('menu.view.hide_minimap') + : t('menu.view.show_minimap')} + + + + + {t('menu.view.zoom_on_scroll')} + + + setScrollAction('zoom')} + > + {t('zoom.on')} + + setScrollAction('pan')} + > + {t('zoom.off')} + + + + + + + {t('menu.view.theme')} + + + setTheme('system')} + > + {t('theme.system')} + + setTheme('light')} + > + {t('theme.light')} + + setTheme('dark')} + > + {t('theme.dark')} + + + + + + + + {t('menu.share.share')} + + + {t('menu.share.export_diagram')} + + + {t('menu.share.import_diagram')} + + + + + + {t('menu.help.help')} + + + {t('menu.help.visit_website')} + + + {t('menu.help.join_discord')} + + + {t('menu.help.schedule_a_call')} + + + + + ); +}; diff --git a/src/pages/editor-page/top-navbar/top-navbar.tsx b/src/pages/editor-page/top-navbar/top-navbar.tsx index 0725465e..db57643a 100644 --- a/src/pages/editor-page/top-navbar/top-navbar.tsx +++ b/src/pages/editor-page/top-navbar/top-navbar.tsx @@ -1,180 +1,18 @@ 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 ChartDBLogo from '@/assets/logo-light.png'; import ChartDBDarkLogo from '@/assets/logo-dark.png'; -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 { useConfig } from '@/hooks/use-config'; -import { IS_CHARTDB_IO } from '@/lib/env'; import { useBreakpoint } from '@/hooks/use-breakpoint'; -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 { DiagramName } from './diagram-name'; import { LastSaved } from './last-saved'; -import { useNavigate } from 'react-router-dom'; import { LanguageNav } from './language-nav/language-nav'; -import { useAlert } from '@/context/alert-context/alert-context'; +import { Menu } from './menu/menu'; export interface TopNavbarProps {} export const TopNavbar: React.FC = () => { - 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, - setShowDependenciesOnCanvas, - showDependenciesOnCanvas, - setShowMiniMapOnCanvas, - showMiniMapOnCanvas, - } = useLocalConfig(); const { effectiveTheme } = useTheme(); - const { t } = useTranslation(); - const { redo, undo, hasRedo, hasUndo } = useHistory(); const { isMd: isDesktop } = useBreakpoint('md'); - const { config, updateConfig } = useConfig(); - const { exportImage } = useExportImage(); - const navigate = useNavigate(); - - const handleDeleteDiagramAction = useCallback(() => { - deleteDiagram(); - navigate('/'); - }, [deleteDiagram, navigate]); - - const createNewDiagram = () => { - openCreateDiagramDialog(); - }; - - const openDiagram = () => { - openOpenDiagramDialog(); - }; - - const exportSVG = useCallback(() => { - exportImage('svg', 1); - }, [exportImage]); - - const exportPNG = useCallback(() => { - openExportImageDialog({ - format: 'png', - }); - }, [openExportImageDialog]); - - const exportJPG = useCallback(() => { - openExportImageDialog({ - format: 'jpeg', - }); - }, [openExportImageDialog]); - - const openChartDBIO = useCallback(() => { - window.location.href = 'https://chartdb.io'; - }, []); - - const openJoinDiscord = useCallback(() => { - window.open('https://discord.gg/QeFwyWSKwC', '_blank'); - }, []); - - const openCalendly = useCallback(() => { - window.open('https://calendly.com/fishner/15min', '_blank'); - }, []); - - const exportSQL = useCallback( - (databaseType: DatabaseType) => { - if (databaseType === DatabaseType.GENERIC) { - openExportSQLDialog({ - targetDatabaseType: DatabaseType.GENERIC, - }); - - return; - } - - if (IS_CHARTDB_IO) { - const now = new Date(); - const lastExportsInLastHalfHour = - config?.exportActions?.filter( - (date) => - now.getTime() - date.getTime() < 30 * 60 * 1000 - ) ?? []; - - if (lastExportsInLastHalfHour.length >= 5) { - showAlert({ - title: 'Export SQL Limit Reached', - content: ( -
-
- We set a budget to allow the community to - check the feature. You have reached the - limit of 5 AI exports every 30min. -
-
- Feel free to use your OPENAI_TOKEN, see the - manual{' '} - - here. - -
-
- ), - closeLabel: 'Close', - }); - return; - } - - updateConfig({ - exportActions: [...lastExportsInLastHalfHour, now], - }); - } - - openExportSQLDialog({ - targetDatabaseType: databaseType, - }); - }, - [config?.exportActions, updateConfig, showAlert, openExportSQLDialog] - ); const renderStars = useCallback(() => { return ( @@ -187,30 +25,10 @@ export const TopNavbar: React.FC = () => { ); }, [isDesktop]); - const showOrHideSidePanel = useCallback(() => { - if (isSidePanelShowed) { - hideSidePanel(); - } else { - showSidePanel(); - } - }, [isSidePanelShowed, showSidePanel, hideSidePanel]); - - const showOrHideCardinality = useCallback(() => { - setShowCardinality(!showCardinality); - }, [showCardinality, setShowCardinality]); - - const showOrHideDependencies = useCallback(() => { - setShowDependenciesOnCanvas(!showDependenciesOnCanvas); - }, [showDependenciesOnCanvas, setShowDependenciesOnCanvas]); - const openBuckleWaitlist = useCallback(() => { window.open('https://waitlist.buckle.dev', '_blank'); }, []); - const showOrHideMiniMap = useCallback(() => { - setShowMiniMapOnCanvas(!showMiniMapOnCanvas); - }, [showMiniMapOnCanvas, setShowMiniMapOnCanvas]); - const renderGetBuckleButton = useCallback(() => { return (