Compare commits

..

15 Commits

Author SHA1 Message Date
Guy Ben-Aharon
74c1730425 chore(main): release 1.6.0 (#512) 2025-01-03 15:41:56 +02:00
Guy Ben-Aharon
94bed7fcce refactor: move menu to a separate component (#517)
* refactor: move menu to a separate component

* merge
2025-01-02 18:11:34 +02:00
Peter
8abf2a7bfc feat(view-menu): add toggle for mini map visibility (#496) (#505)
* feat(view-menu): add toggle for mini map visibility (#496)

* fix build

* fix build

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2025-01-02 17:56:37 +02:00
Guy Ben-Aharon
ee659eaa03 fix: add loadDiagramFromData logic to chartdb provider (#513) 2025-01-01 10:48:42 +02:00
Guy Ben-Aharon
7c5db0848e fix(dependency): upgrade react query to v7 - clean console warnings (#504) 2024-12-31 12:18:58 +02:00
Kerillos Iskandar
4b43f720e9 fix(i18n): translation/Arabic (#509)
* translation/Arabic

* fix lint

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-12-30 18:59:07 +02:00
Kareem Adel
766b5164b8 Add Arabic Language & Enhancements to UI/UX (#507)
* * set nullable option to false
* add visual representing for nullable
* add visual icon for comment
* set showCardinality to true by default
* removed the trash button from the field when hovering for best UX, it's already exist in the context menu

* refactor: improve formatting and structure of TableNodeField component

* ### Translation
- Add Arabic support
- Change languages names sorting for clarity.

### Enhancements
- set the nullable option to false
- add visual representation for nullable
- add a visual icon for comments (show the comment content by hovering)
- set showCardinality to true by default
- removed the trash button from the field when hovering for best UX, it already exists in the context menu

* refactor: standardize string formatting in Arabic locale files

* some alignments

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-12-26 20:00:56 +02:00
Guy Ben-Aharon
7868ca9f42 change last save to icon (#506) 2024-12-26 11:07:50 +02:00
Guy Ben-Aharon
0411742864 Fix multiple dialog pop-ups (#501) 2024-12-24 15:00:55 +02:00
Guy Ben-Aharon
9831ac5a10 add buckle dialog (#499) 2024-12-23 20:56:10 +02:00
Guy Ben-Aharon
91c6fb9249 add buckle dialog (#498) 2024-12-23 20:31:58 +02:00
Guy Ben-Aharon
c155013668 change label (#494) 2024-12-22 23:06:40 +02:00
Guy Ben-Aharon
1b0f293c87 remove diagrams from navbar + buckle (#490) 2024-12-22 19:52:16 +02:00
Guy Ben-Aharon
df2dc03aa0 open diagram dialog resizing (#493) 2024-12-22 16:13:29 +02:00
Guy Ben-Aharon
205d431c89 separate alert dialog from dialog context (#491) 2024-12-22 15:10:42 +02:00
47 changed files with 1470 additions and 655 deletions

View File

@@ -1,5 +1,19 @@
# Changelog
## [1.6.0](https://github.com/chartdb/chartdb/compare/v1.5.1...v1.6.0) (2025-01-02)
### Features
* **view-menu:** add toggle for mini map visibility ([#496](https://github.com/chartdb/chartdb/issues/496)) ([#505](https://github.com/chartdb/chartdb/issues/505)) ([8abf2a7](https://github.com/chartdb/chartdb/commit/8abf2a7bfcc36d39e60ac133b0e5e569de1bbc72))
### Bug Fixes
* add loadDiagramFromData logic to chartdb provider ([#513](https://github.com/chartdb/chartdb/issues/513)) ([ee659ea](https://github.com/chartdb/chartdb/commit/ee659eaa038a94ee13801801e84152df4d79683d))
* **dependency:** upgrade react query to v7 - clean console warnings ([#504](https://github.com/chartdb/chartdb/issues/504)) ([7c5db08](https://github.com/chartdb/chartdb/commit/7c5db0848e49dfdb7e7120f77003d1e37f8d71b0))
* **i18n:** translation/Arabic ([#509](https://github.com/chartdb/chartdb/issues/509)) ([4b43f72](https://github.com/chartdb/chartdb/commit/4b43f720e90e49d5461e68d188e3865000f52497))
## [1.5.1](https://github.com/chartdb/chartdb/compare/v1.5.0...v1.5.1) (2024-12-15)

78
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "chartdb",
"version": "1.5.1",
"version": "1.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "chartdb",
"version": "1.5.1",
"version": "1.6.0",
"dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@dnd-kit/sortable": "^8.0.0",
@@ -56,7 +56,7 @@
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",
"react-responsive": "^10.0.0",
"react-router-dom": "^6.26.0",
"react-router-dom": "^7.1.1",
"react-use": "^17.5.1",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
@@ -3042,15 +3042,6 @@
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
"license": "MIT"
},
"node_modules/@remix-run/router": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
"integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
@@ -3320,6 +3311,12 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
@@ -5037,6 +5034,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/copy-to-clipboard": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
@@ -8567,35 +8573,43 @@
}
},
"node_modules/react-router": {
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz",
"integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz",
"integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.19.2"
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
},
"engines": {
"node": ">=14.0.0"
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=16.8"
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz",
"integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.1.tgz",
"integrity": "sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.19.2",
"react-router": "6.26.2"
"react-router": "7.1.1"
},
"engines": {
"node": ">=14.0.0"
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-style-singleton": {
@@ -8966,6 +8980,12 @@
"node": ">=10"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -9725,6 +9745,12 @@
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"license": "0BSD"
},
"node_modules/turbo-stream": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
"license": "ISC"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "chartdb",
"private": true,
"version": "1.5.1",
"version": "1.6.0",
"type": "module",
"scripts": {
"dev": "vite",
@@ -60,7 +60,7 @@
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",
"react-responsive": "^10.0.0",
"react-router-dom": "^6.26.0",
"react-router-dom": "^7.1.1",
"react-use": "^17.5.1",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",

BIN
public/buckle-animated.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

BIN
public/buckle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,15 @@
import { createContext, useContext } from 'react';
import { emptyFn } from '@/lib/utils';
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
export interface AlertContext {
showAlert: (params: BaseAlertDialogProps) => void;
closeAlert: () => void;
}
export const alertContext = createContext<AlertContext>({
closeAlert: emptyFn,
showAlert: emptyFn,
});
export const useAlert = () => useContext(alertContext);

View File

@@ -0,0 +1,36 @@
import React, { useCallback, useState } from 'react';
import type { AlertContext } from './alert-context';
import { alertContext } from './alert-context';
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
import { BaseAlertDialog } from '@/dialogs/base-alert-dialog/base-alert-dialog';
export const AlertProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [showAlert, setShowAlert] = useState(false);
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
title: '',
});
const showAlertHandler: AlertContext['showAlert'] = useCallback(
(params) => {
setAlertParams(params);
setShowAlert(true);
},
[setShowAlert, setAlertParams]
);
const closeAlertHandler = useCallback(() => {
setShowAlert(false);
}, [setShowAlert]);
return (
<alertContext.Provider
value={{
showAlert: showAlertHandler,
closeAlert: closeAlertHandler,
}}
>
{children}
<BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} />
</alertContext.Provider>
);
};

View File

@@ -84,6 +84,7 @@ export interface ChartDBContext {
options?: { updateHistory: boolean }
) => Promise<void>;
loadDiagram: (diagramId: string) => Promise<Diagram | undefined>;
loadDiagramFromData: (diagram: Diagram) => void;
updateDiagramUpdatedAt: () => Promise<void>;
clearDiagramData: () => Promise<void>;
deleteDiagram: () => Promise<void>;
@@ -246,6 +247,7 @@ export const chartDBContext = createContext<ChartDBContext>({
updateDiagramName: emptyFn,
updateDiagramUpdatedAt: emptyFn,
loadDiagram: emptyFn,
loadDiagramFromData: emptyFn,
clearDiagramData: emptyFn,
deleteDiagram: emptyFn,

View File

@@ -1336,15 +1336,9 @@ export const ChartDBProvider: React.FC<
]
);
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
async (diagramId: string) => {
const diagram = await db.getDiagram(diagramId, {
includeRelationships: true,
includeTables: true,
includeDependencies: true,
});
if (diagram) {
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
useCallback(
async (diagram) => {
setDiagramId(diagram.id);
setDiagramName(diagram.name);
setDatabaseType(diagram.databaseType);
@@ -1356,23 +1350,36 @@ export const ChartDBProvider: React.FC<
setDiagramUpdatedAt(diagram.updatedAt);
events.emit({ action: 'load_diagram', data: { diagram } });
},
[
setDiagramId,
setDiagramName,
setDatabaseType,
setDatabaseEdition,
setTables,
setRelationships,
setDependencies,
setDiagramCreatedAt,
setDiagramUpdatedAt,
events,
]
);
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
async (diagramId: string) => {
const diagram = await db.getDiagram(diagramId, {
includeRelationships: true,
includeTables: true,
includeDependencies: true,
});
if (diagram) {
loadDiagramFromData(diagram);
}
return diagram;
},
[
db,
setDiagramId,
setDiagramName,
setDatabaseType,
setDatabaseEdition,
setTables,
setRelationships,
setDependencies,
setDiagramCreatedAt,
setDiagramUpdatedAt,
events,
]
[db, loadDiagramFromData]
);
return (
@@ -1393,6 +1400,7 @@ export const ChartDBProvider: React.FC<
updateDiagramId,
updateDiagramName,
loadDiagram,
loadDiagramFromData,
updateDatabaseType,
updateDatabaseEdition,
clearDiagramData,

View File

@@ -1,6 +1,5 @@
import { createContext } from 'react';
import { emptyFn } from '@/lib/utils';
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
import type { TableSchemaDialogProps } from '@/dialogs/table-schema-dialog/table-schema-dialog';
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
@@ -21,10 +20,6 @@ export interface DialogContext {
openExportSQLDialog: (params: Omit<ExportSQLDialogProps, 'dialog'>) => void;
closeExportSQLDialog: () => void;
// Alert dialog
showAlert: (params: BaseAlertDialogProps) => void;
closeAlert: () => void;
// Create relationship dialog
openCreateRelationshipDialog: () => void;
closeCreateRelationshipDialog: () => void;
@@ -45,6 +40,10 @@ export interface DialogContext {
openStarUsDialog: () => void;
closeStarUsDialog: () => void;
// Buckle dialog
openBuckleDialog: () => void;
closeBuckleDialog: () => void;
// Export image dialog
openExportImageDialog: (
params: Omit<ExportImageDialogProps, 'dialog'>
@@ -71,8 +70,6 @@ export const dialogContext = createContext<DialogContext>({
closeOpenDiagramDialog: emptyFn,
openExportSQLDialog: emptyFn,
closeExportSQLDialog: emptyFn,
closeAlert: emptyFn,
showAlert: emptyFn,
closeCreateRelationshipDialog: emptyFn,
openCreateRelationshipDialog: emptyFn,
openImportDatabaseDialog: emptyFn,
@@ -87,4 +84,6 @@ export const dialogContext = createContext<DialogContext>({
closeExportDiagramDialog: emptyFn,
openImportDiagramDialog: emptyFn,
closeImportDiagramDialog: emptyFn,
openBuckleDialog: emptyFn,
closeBuckleDialog: emptyFn,
});

View File

@@ -6,8 +6,6 @@ import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-di
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
import { DatabaseType } from '@/lib/domain/database-type';
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
import { BaseAlertDialog } from '@/dialogs/base-alert-dialog/base-alert-dialog';
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
import { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
@@ -19,6 +17,7 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import { BuckleDialog } from '@/dialogs/buckle-dialog/buckle-dialog';
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -29,6 +28,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
useState(false);
const [openStarUsDialog, setOpenStarUsDialog] = useState(false);
const [openBuckleDialog, setOpenBuckleDialog] = useState(false);
// Export image dialog
const [openExportImageDialog, setOpenExportImageDialog] = useState(false);
@@ -96,22 +96,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
useState(false);
// Alert dialog
const [showAlert, setShowAlert] = useState(false);
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
title: '',
});
const showAlertHandler: DialogContext['showAlert'] = useCallback(
(params) => {
setAlertParams(params);
setShowAlert(true);
},
[setShowAlert, setAlertParams]
);
const closeAlertHandler = useCallback(() => {
setShowAlert(false);
}, [setShowAlert]);
return (
<dialogContext.Provider
value={{
@@ -121,8 +105,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
openExportSQLDialog: openExportSQLDialogHandler,
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
showAlert: showAlertHandler,
closeAlert: closeAlertHandler,
openCreateRelationshipDialog: () =>
setOpenCreateRelationshipDialog(true),
closeCreateRelationshipDialog: () =>
@@ -134,6 +116,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeTableSchemaDialog: () => setOpenTableSchemaDialog(false),
openStarUsDialog: () => setOpenStarUsDialog(true),
closeStarUsDialog: () => setOpenStarUsDialog(false),
closeBuckleDialog: () => setOpenBuckleDialog(false),
openBuckleDialog: () => setOpenBuckleDialog(true),
closeExportImageDialog: () => setOpenExportImageDialog(false),
openExportImageDialog: openExportImageDialogHandler,
openExportDiagramDialog: () => setOpenExportDiagramDialog(true),
@@ -151,7 +135,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
dialog={{ open: openExportSQLDialog }}
{...exportSQLDialogParams}
/>
<BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} />
<CreateRelationshipDialog
dialog={{ open: openCreateRelationshipDialog }}
/>
@@ -170,6 +153,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
/>
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
<BuckleDialog dialog={{ open: openBuckleDialog }} />
</dialogContext.Provider>
);
};

View File

@@ -30,8 +30,17 @@ export interface LocalConfigContext {
starUsDialogLastOpen: number;
setStarUsDialogLastOpen: (lastOpen: number) => void;
buckleWaitlistOpened: boolean;
setBuckleWaitlistOpened: (githubRepoOpened: boolean) => void;
buckleDialogLastOpen: number;
setBuckleDialogLastOpen: (lastOpen: number) => void;
showDependenciesOnCanvas: boolean;
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
showMiniMapOnCanvas: boolean;
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
}
export const LocalConfigContext = createContext<LocalConfigContext>({
@@ -56,6 +65,15 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
starUsDialogLastOpen: 0,
setStarUsDialogLastOpen: emptyFn,
buckleWaitlistOpened: false,
setBuckleWaitlistOpened: emptyFn,
buckleDialogLastOpen: 0,
setBuckleDialogLastOpen: emptyFn,
showDependenciesOnCanvas: false,
setShowDependenciesOnCanvas: emptyFn,
showMiniMapOnCanvas: false,
setShowMiniMapOnCanvas: emptyFn,
});

View File

@@ -10,7 +10,10 @@ const showCardinalityKey = 'show_cardinality';
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
const githubRepoOpenedKey = 'github_repo_opened';
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
const buckleWaitlistOpenedKey = 'buckle_waitlist_opened';
const buckleDialogLastOpenKey = 'buckle_dialog_last_open';
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
const showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -48,12 +51,28 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
);
const [buckleWaitlistOpened, setBuckleWaitlistOpened] =
React.useState<boolean>(
(localStorage.getItem(buckleWaitlistOpenedKey) || 'false') ===
'true'
);
const [buckleDialogLastOpen, setBuckleDialogLastOpen] =
React.useState<number>(
parseInt(localStorage.getItem(buckleDialogLastOpenKey) || '0')
);
const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] =
React.useState<boolean>(
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
'true'
);
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
React.useState<boolean>(
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
);
useEffect(() => {
localStorage.setItem(
starUsDialogLastOpenKey,
@@ -65,6 +84,20 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
}, [githubRepoOpened]);
useEffect(() => {
localStorage.setItem(
buckleDialogLastOpenKey,
buckleDialogLastOpen.toString()
);
}, [buckleDialogLastOpen]);
useEffect(() => {
localStorage.setItem(
buckleWaitlistOpenedKey,
buckleWaitlistOpened.toString()
);
}, [buckleWaitlistOpened]);
useEffect(() => {
localStorage.setItem(
hideMultiSchemaNotificationKey,
@@ -95,6 +128,13 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
);
}, [showDependenciesOnCanvas]);
useEffect(() => {
localStorage.setItem(
showMiniMapOnCanvasKey,
showMiniMapOnCanvas.toString()
);
}, [showMiniMapOnCanvas]);
return (
<LocalConfigContext.Provider
value={{
@@ -114,6 +154,12 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
setStarUsDialogLastOpen,
showDependenciesOnCanvas,
setShowDependenciesOnCanvas,
setBuckleDialogLastOpen,
buckleDialogLastOpen,
buckleWaitlistOpened,
setBuckleWaitlistOpened,
showMiniMapOnCanvas,
setShowMiniMapOnCanvas,
}}
>
{children}

View File

@@ -10,7 +10,7 @@ import {
AlertDialogTitle,
} from '@/components/alert-dialog/alert-dialog';
import type { AlertDialogProps } from '@radix-ui/react-alert-dialog';
import { useDialog } from '@/hooks/use-dialog';
import { useAlert } from '@/context/alert-context/alert-context';
export interface BaseAlertDialogProps {
title: string;
@@ -33,7 +33,7 @@ export const BaseAlertDialog: React.FC<BaseAlertDialogProps> = ({
content,
onClose,
}) => {
const { closeAlert } = useDialog();
const { closeAlert } = useAlert();
const closeAlertHandler = useCallback(() => {
onClose?.();

View File

@@ -0,0 +1,80 @@
import React, { useCallback, useEffect } from 'react';
import { useDialog } from '@/hooks/use-dialog';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/dialog/dialog';
import { Button } from '@/components/button/button';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useLocalConfig } from '@/hooks/use-local-config';
import { useTheme } from '@/hooks/use-theme';
export interface BuckleDialogProps extends BaseDialogProps {}
export const BuckleDialog: React.FC<BuckleDialogProps> = ({ dialog }) => {
const { setBuckleWaitlistOpened } = useLocalConfig();
const { effectiveTheme } = useTheme();
useEffect(() => {
if (!dialog.open) return;
}, [dialog.open]);
const { closeBuckleDialog } = useDialog();
const handleConfirm = useCallback(() => {
setBuckleWaitlistOpened(true);
window.open('https://waitlist.buckle.dev', '_blank');
}, [setBuckleWaitlistOpened]);
return (
<Dialog
{...dialog}
onOpenChange={(open) => {
if (!open) {
closeBuckleDialog();
}
}}
>
<DialogContent
className="flex flex-col"
showClose={false}
onInteractOutside={(e) => {
e.preventDefault();
}}
>
<DialogHeader>
<DialogTitle className="hidden" />
<DialogDescription className="hidden" />
</DialogHeader>
<div className="flex w-full flex-col items-center">
<img
src={
effectiveTheme === 'light'
? '/buckle-animated.gif'
: '/buckle.png'
}
className="h-16"
/>
<div className="mt-6 text-center text-base">
We've been working on something big -{' '}
<span className="font-semibold">Ready to explore?</span>
</div>
</div>
<DialogFooter className="flex gap-1 md:justify-between">
<DialogClose asChild>
<Button variant="secondary">Not now</Button>
</DialogClose>
<DialogClose asChild>
<Button onClick={handleConfirm}>
Try ChartDB v2.0!
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -12,6 +12,7 @@ import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
import { Trans, useTranslation } from 'react-i18next';
import { useReactFlow } from '@xyflow/react';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useAlert } from '@/context/alert-context/alert-context';
export interface ImportDatabaseDialogProps extends BaseDialogProps {
databaseType: DatabaseType;
@@ -21,7 +22,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
dialog,
databaseType,
}) => {
const { closeImportDatabaseDialog, showAlert } = useDialog();
const { closeImportDatabaseDialog } = useDialog();
const { showAlert } = useAlert();
const {
tables,
relationships,

View File

@@ -74,7 +74,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
}}
>
<DialogContent
className="flex h-[30rem] max-h-screen w-[90vw] flex-col overflow-y-auto md:w-screen xl:min-w-[55vw]"
className="flex h-[30rem] max-h-screen flex-col overflow-y-auto md:min-w-[80vw] xl:min-w-[55vw]"
showClose
>
<DialogHeader>

View File

@@ -73,3 +73,64 @@
@apply dark:group-hover:bg-slate-900 group-hover:bg-slate-100 group-hover:ring-[0.5px] rounded-md cursor-pointer;
}
}
.gradient-background {
/* Fallback: Set a background color. */
background-color: #f46b24;
/* Create the gradient. */
background-image: linear-gradient(
45deg,
#2e6579 20%,
#4fafca 20%,
#4fafca 40%,
#6dc630 40%,
#6dc630 60%,
#f9dc3a 60%,
#f9dc3a 80%,
#f46b24 80%
);
/* Set the background size and repeat properties. */
background-size: 100%;
background-repeat: repeat;
/* Use the text as a mask for the background. */
/* This will show the gradient as a text color rather than element bg. */
/* -webkit-background-clip: text;
-webkit-text-fill-color: transparent; */
/* Animate the text when loading the element. */
/* This animates it on page load and when hovering out. */
animation: rainbow-text-simple-animation-rev 0.75s ease forwards;
}
.gradient-background:hover {
animation: rainbow-text-simple-animation 0.5s ease-in forwards;
}
@keyframes rainbow-text-simple-animation-rev {
0% {
background-size: 650%;
}
40% {
background-size: 650%;
}
100% {
background-size: 100%;
}
}
/* Move the background and make it larger. */
/* Animation shown when hovering over the text. */
@keyframes rainbow-text-simple-animation {
0% {
background-size: 100%;
}
80% {
background-size: 650%;
}
100% {
background-size: 650%;
}
}

View File

@@ -22,6 +22,7 @@ import { te, teMetadata } from './locales/te';
import { bn, bnMetadata } from './locales/bn';
import { gu, guMetadata } from './locales/gu';
import { vi, viMetadata } from './locales/vi';
import { ar, arMetadata } from './locales/ar';
export const languages: LanguageMetadata[] = [
enMetadata,
@@ -44,6 +45,7 @@ export const languages: LanguageMetadata[] = [
bnMetadata,
guMetadata,
viMetadata,
arMetadata,
];
const resources = {
@@ -67,6 +69,7 @@ const resources = {
bn,
gu,
vi,
ar,
};
i18n.use(LanguageDetector)

407
src/i18n/locales/ar.ts Normal file
View File

@@ -0,0 +1,407 @@
import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ar: LanguageTranslation = {
translation: {
menu: {
file: {
file: 'ملف',
new: 'جديد',
open: 'فتح',
save: 'حفظ',
import_database: 'استيراد قاعدة بيانات',
export_sql: 'SQL تصدير',
export_as: 'تصدير كـ',
delete_diagram: 'حذف الرسم البياني',
exit: 'خروج',
},
edit: {
edit: 'تحرير',
undo: 'تراجع',
redo: 'إعادة',
clear: 'مسح',
},
view: {
view: 'عرض',
show_sidebar: 'إظهار الشريط الجانبي',
hide_sidebar: 'إخفاء الشريط الجانبي',
hide_cardinality: 'إخفاء الكاردينالية',
show_cardinality: 'إظهار الكاردينالية',
zoom_on_scroll: 'تكبير/تصغير عند التمرير',
theme: 'المظهر',
show_dependencies: 'إظهار الاعتمادات',
hide_dependencies: 'إخفاء الاعتمادات',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: 'مشاركة',
export_diagram: 'تصدير المخطط',
import_diagram: 'استيراد المخطط',
},
help: {
help: 'مساعدة',
visit_website: 'ChartDB قم بزيارة',
join_discord: 'Discord انضم إلينا على',
schedule_a_call: '!تحدث معنا',
},
},
delete_diagram_alert: {
title: 'حذف المخطط',
description:
'.لا يمكن التراجع عن هذا الإجراء. سيتم حذف الرسم البياني بشكل دائم',
cancel: 'إلغاء',
delete: 'حذف',
},
clear_diagram_alert: {
title: 'مسح الرسم البياني',
description:
'.لا يمكن التراجع عن هذا الاجراء. سيتم حذف جميع البيانات في الرسم البياني بشكل دائم',
cancel: 'إلغاء',
clear: 'مسح',
},
reorder_diagram_alert: {
title: 'إعادة ترتيب الرسم البياني',
description:
'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
reorder: 'إعادة ترتيب',
cancel: 'إلغاء',
},
multiple_schemas_alert: {
title: 'مخططات متعددة',
description:
'{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
dont_show_again: 'لا تظهره مجدداً',
change_schema: 'تغيير',
none: 'لا شيء',
},
copy_to_clipboard_toast: {
unsupported: {
title: 'فشل النسخ',
description: '.الحافظة غير مدعومة',
},
failed: {
title: 'فشل النسخ',
description: 'حدث خطأ أثناء النسخ. حاول مجدداً',
},
},
theme: {
system: 'النظام',
light: 'فاتح',
dark: 'داكن',
},
zoom: {
on: 'تشغيل',
off: 'إيقاف',
},
last_saved: 'آخر حفظ',
saved: 'تم الحفظ',
loading_diagram: '...جارِ تحميل الرسم البياني',
deselect_all: 'إلغاء تحديد الكل',
select_all: 'تحديد الكل',
clear: 'مسح',
show_more: 'عرض المزيد',
show_less: 'عرض أقل',
copy_to_clipboard: 'نسخ إلى الحافظة',
copied: '!تم النسخ',
side_panel: {
schema: ':المخطط',
filter_by_schema: 'تصفية حسب المخطط',
search_schema: '...بحث في المخطط',
no_schemas_found: '.لم يتم العثور على مخططات',
view_all_options: '...عرض جميع الخيارات',
tables_section: {
tables: 'الجداول',
add_table: 'إضافة جدول',
filter: 'تصفية',
collapse: 'طي الكل',
table: {
fields: 'الحقول',
nullable: 'يمكن ان يكون فارغاً؟',
primary_key: 'المفتاح الأساسي',
indexes: 'الفهارس',
comments: 'تعليقات',
no_comments: 'لا توجد تعليقات',
add_field: 'إضافة حقل',
add_index: 'إضافة فهرس',
index_select_fields: 'حدد الحقول',
no_types_found: 'لا يوجد أنواع',
field_name: 'الإسم',
field_type: 'النوع',
field_actions: {
title: 'خصائص الحقل',
unique: 'فريد',
comments: 'تعليقات',
no_comments: 'لا يوجد تعليقات',
delete_field: 'حذف الحقل',
},
index_actions: {
title: 'خصائص الفهرس',
name: 'الإسم',
unique: 'فريد',
delete_index: 'حذف الفهرس',
},
table_actions: {
title: 'إجراءات الجدول',
change_schema: 'تغيير المخطط',
add_field: 'إضافة حقل',
add_index: 'إضافة فهرس',
duplicate_table: 'نسخ الجدول',
delete_table: 'حذف الجدول',
},
},
empty_state: {
title: 'لا توجد جداول',
description: 'أنشئ جدولاً للبدء',
},
},
relationships_section: {
relationships: 'العلاقات',
filter: 'تصفية',
add_relationship: 'إضافة علاقة',
collapse: 'طي الكل',
relationship: {
primary: 'الجدول الأساسي',
foreign: 'الجدول المرتبط',
cardinality: 'الكاردينالية',
delete_relationship: 'حذف',
relationship_actions: {
title: 'إجراءات',
delete_relationship: 'حذف',
},
},
empty_state: {
title: 'لا توجد علاقات',
description: 'إنشئ علاقة لربط الجداول',
},
},
dependencies_section: {
dependencies: 'الاعتمادات',
filter: 'تصفية',
collapse: 'طي الكل',
dependency: {
table: 'الجدول',
dependent_table: 'عرض الاعتمادات',
delete_dependency: 'حذف',
dependency_actions: {
title: 'إجراءات',
delete_dependency: 'حذف',
},
},
empty_state: {
title: 'لا توجد اعتمادات',
description: 'إنشاء اعتماد للبدء',
},
},
},
toolbar: {
zoom_in: 'تكبير',
zoom_out: 'تصغير',
save: 'حفظ',
show_all: 'عرض الكل',
undo: 'تراجع',
redo: 'إعادة',
reorder_diagram: 'إعادة ترتيب الرسم البياني',
highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
},
new_diagram_dialog: {
database_selection: {
title: 'ما هو نوع قاعدة البيانات الخاصة بك؟',
description:
'تتمتع كل قاعدة بيانات بمميزاتها وقدراتها الفريدة.',
check_examples_long: 'ألقي نظرة على الأمثلة',
check_examples_short: 'أمثلة',
},
import_database: {
title: 'إسترد قاعدة بياناتك',
database_edition: ':إصدار قاعدة البيانات',
step_1: ':قم بتشغيل هذا البرنامج النصي في قاعدة بياناتك',
step_2: ':إلصق نتيجة البرنامج النصي هنا',
script_results_placeholder: '...نتيجة البرنامج النصي هنا',
ssms_instructions: {
button_text: 'SSMS تعليمات',
title: 'تعليمات',
step_1: 'SQL SERVER < انتقل إلى الأدوات > الخيارات > نتائح الاستعلام',
step_2: '(اضبطها على 9999999) XML اذا كنت تستخدم "نتائج إلى الشبكة"، قم بتغيير الحد الاقصى للاحرف المستردة للبيانات غير',
},
instructions_link: 'تحتاج مساعدة؟ شاهد الفيديو',
check_script_result: 'تحقق من نتيجة البرنامج النصي',
},
cancel: 'إلغاء',
import_from_file: 'استيراد من ملف',
back: 'رجوع',
empty_diagram: 'مخطط فارغ',
continue: 'متابعة',
import: 'استيراد',
},
open_diagram_dialog: {
title: 'فتح مخطط',
description: 'اختر مخططًا لفتحه من القائمة ادناه',
table_columns: {
name: 'الإسم',
created_at: 'تاريخ الإنشاء',
last_modified: 'آخر تعديل',
tables_count: 'الجداول',
},
cancel: 'إلغاء',
open: 'فتح',
},
export_sql_dialog: {
title: 'SQL تصدير',
description:
'{{databaseType}} صدّر مخطط الرسم البياني إلى برنامج نصي لـ',
close: 'إغلاق',
loading: {
text: '...{{databaseType}} ل SQL يقوم الذكاء الاصطناعي بإنشاء',
description: 'هذا قد يستغرق 30 ثانية',
},
error: {
message:
'النصي. يرجى المحاولة مرة اخرى لاحقاً او <0>اتصل بنا</0> SQL خطأ في إنشاء برنامج',
description:
' الخاصة بك. راجع الدليل <0>هنا</0> OPENAI_TOKEN لا تتردد في استخدام',
},
},
create_relationship_dialog: {
title: 'إنشاء علاقة',
primary_table: 'الجدول الأساسي',
primary_field: 'الحقل الأساسي',
referenced_table: 'الجدول المرتبط',
referenced_field: 'الحقل المرتبط',
primary_table_placeholder: 'حدد الجدول',
primary_field_placeholder: 'حدد الحقل',
referenced_table_placeholder: 'حدد الجدول',
referenced_field_placeholder: 'حدد الحقل',
no_tables_found: 'لم يتم العثور على جداول',
no_fields_found: 'لم يتم العثور على حقول',
create: 'إنشاء',
cancel: 'إلغاء',
},
import_database_dialog: {
title: 'استيراد إلى المخطط الحالي',
override_alert: {
title: 'استيراد قاعدة بيانات',
content: {
alert: 'سيؤدي استيراد هذا المخطط إلى التأثير على الجداول والعلاقات الحالية.',
new_tables:
'جداول جديدة <bold>{{newTablesNumber}}</bold> سيتم إضافة',
new_relationships:
'علاقات جديدة <bold>{{newRelationshipsNumber}}</bold> سيتم إنشاء',
tables_override:
'جداول <bold>{{tablesOverrideNumber}}</bold> سيتم تعديل',
proceed: 'هل تريد المتابعة؟',
},
import: 'استيراد',
cancel: 'إلغاء',
},
},
export_image_dialog: {
title: 'تصدير الصورة',
description: ':اختر عامل المقياس للتصدير',
scale_1x: '1x عادي',
scale_2x: '2x (موصى به)',
scale_3x: '3x',
scale_4x: '4x',
cancel: 'إلغاء',
export: 'تصدير',
},
new_table_schema_dialog: {
title: 'اختر مخططاً',
description:
'.يتم حالياً عرض مخططات متعددة. اختر واحداً للجدول الجديد',
cancel: 'إلغاء',
confirm: 'تأكيد',
},
update_table_schema_dialog: {
title: 'تغيير المخطط',
description: '"{{tableName}}" تحديث مخطط الجدول',
cancel: 'إلغاء',
confirm: 'تغيير',
},
star_us_dialog: {
title: '!ساعدنا على التحسن',
description: '؟! إنها مجرد نقرة واحدةGITHUB هل ترغب في تقييمنا على',
close: 'ليس الآن',
confirm: '!بالتأكيد',
},
export_diagram_dialog: {
title: 'تصدير المخطط',
description: ':اختر التنسيق للتصدير',
format_json: 'JSON',
cancel: 'إلغاء',
export: 'تصدير',
error: {
title: 'حدث خطأ أثناء التصدير',
description:
'chartdb.io@gmail.com حدث خطأ ما. هل تحتاج إلى مساعدة؟',
},
},
import_diagram_dialog: {
title: 'استيراد الرسم البياني',
description: ':للرسم البياني ادناه JSON قم بلصق',
cancel: 'إلغاء',
import: 'استيراد',
error: {
title: 'حدث خطأ أثناء الاستيراد',
description:
'chartdb.io@gmail.com و المحاولة مرة اخرى. هل تحتاج إلى المساعدة؟ JSON غير صالح. يرجى التحقق من JSON الرسم البياني',
},
},
relationship_type: {
one_to_one: 'واحد إلى واحد',
one_to_many: 'واحد إلى متعدد',
many_to_one: 'متعدد إلى واحد',
many_to_many: 'متعدد إلى متعدد',
},
canvas_context_menu: {
new_table: 'جدول جديد',
new_relationship: 'علاقة جديدة',
},
table_node_context_menu: {
edit_table: 'تعديل الجدول',
duplicate_table: 'نسخ الجدول',
delete_table: 'حذف الجدول',
},
snap_to_grid_tooltip: '({{key}} مغنظة الشبكة (اضغط مع الاستمرار على',
tool_tips: {
double_click_to_edit: 'انقر مرتين للتعديل',
},
language_select: {
change_language: 'اللغة',
},
},
};
export const arMetadata: LanguageMetadata = {
name: 'Arabic',
nativeName: 'العربية',
code: 'ar',
};

View File

@@ -30,6 +30,9 @@ export const bn: LanguageTranslation = {
theme: 'থিম',
show_dependencies: 'নির্ভরতাগুলি দেখান',
hide_dependencies: 'নির্ভরতাগুলি লুকান',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
@@ -102,7 +105,6 @@ export const bn: LanguageTranslation = {
last_saved: 'সর্বশেষ সংরক্ষণ',
saved: 'সংরক্ষিত',
diagrams: 'ডায়াগ্রাম',
loading_diagram: 'ডায়াগ্রাম লোড হচ্ছে...',
deselect_all: 'সব নির্বাচন সরান',
select_all: 'সব নির্বাচন করুন',

View File

@@ -30,6 +30,9 @@ export const de: LanguageTranslation = {
theme: 'Stil',
show_dependencies: 'Abhängigkeiten anzeigen',
hide_dependencies: 'Abhängigkeiten ausblenden',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -103,7 +106,6 @@ export const de: LanguageTranslation = {
last_saved: 'Zuletzt gespeichert',
saved: 'Gespeichert',
diagrams: 'Diagramme',
loading_diagram: 'Diagramm wird geladen...',
deselect_all: 'Alles abwählen',
select_all: 'Alles auswählen',

View File

@@ -30,6 +30,8 @@ export const en = {
theme: 'Theme',
show_dependencies: 'Show Dependencies',
hide_dependencies: 'Hide Dependencies',
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: 'Share',
@@ -101,7 +103,6 @@ export const en = {
last_saved: 'Last saved',
saved: 'Saved',
diagrams: 'Diagrams',
loading_diagram: 'Loading diagram...',
deselect_all: 'Deselect All',
select_all: 'Select All',

View File

@@ -30,6 +30,9 @@ export const es: LanguageTranslation = {
theme: 'Tema',
show_dependencies: 'Mostrar dependencias',
hide_dependencies: 'Ocultar dependencias',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -93,7 +96,6 @@ export const es: LanguageTranslation = {
last_saved: 'Último guardado',
saved: 'Guardado',
diagrams: 'Diagramas',
loading_diagram: 'Cargando diagrama...',
deselect_all: 'Deseleccionar todo',
select_all: 'Seleccionar todo',

View File

@@ -30,6 +30,9 @@ export const fr: LanguageTranslation = {
theme: 'Thème',
show_dependencies: 'Afficher les Dépendances',
hide_dependencies: 'Masquer les Dépendances',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: 'Partage',
@@ -92,7 +95,6 @@ export const fr: LanguageTranslation = {
last_saved: 'Dernière sauvegarde',
saved: 'Enregistré',
diagrams: 'Diagrammes',
loading_diagram: 'Chargement du diagramme...',
deselect_all: 'Tout désélectionner',
select_all: 'Tout sélectionner',

View File

@@ -30,6 +30,9 @@ export const gu: LanguageTranslation = {
theme: 'થિમ',
show_dependencies: 'નિર્ભરતાઓ બતાવો',
hide_dependencies: 'નિર્ભરતાઓ છુપાવો',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
@@ -102,7 +105,6 @@ export const gu: LanguageTranslation = {
last_saved: 'છેલ્લે સાચવ્યું',
saved: 'સાચવ્યું',
diagrams: 'ડાયાગ્રામ',
loading_diagram: 'ડાયાગ્રામ લોડ થઈ રહ્યું છે...',
deselect_all: 'બધાને ડીસેલેક્ટ કરો',
select_all: 'બધા પસંદ કરો',

View File

@@ -30,6 +30,9 @@ export const hi: LanguageTranslation = {
theme: 'थीम',
show_dependencies: 'निर्भरता दिखाएँ',
hide_dependencies: 'निर्भरता छिपाएँ',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -102,7 +105,6 @@ export const hi: LanguageTranslation = {
last_saved: 'अंतिम सहेजा गया',
saved: 'सहेजा गया',
diagrams: 'आरेख',
loading_diagram: 'आरेख लोड हो रहा है...',
deselect_all: 'सभी को अचयनित करें',
select_all: 'सभी को चुनें',

View File

@@ -30,6 +30,9 @@ export const id_ID: LanguageTranslation = {
theme: 'Tema',
show_dependencies: 'Tampilkan Dependensi',
hide_dependencies: 'Sembunyikan Dependensi',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: 'Bagikan',
@@ -101,7 +104,6 @@ export const id_ID: LanguageTranslation = {
last_saved: 'Terakhir disimpan',
saved: 'Tersimpan',
diagrams: 'Diagram',
loading_diagram: 'Memuat diagram...',
deselect_all: 'Batalkan Semua',
select_all: 'Pilih Semua',

View File

@@ -31,6 +31,9 @@ export const ja: LanguageTranslation = {
// TODO: Translate
show_dependencies: 'Show Dependencies',
hide_dependencies: 'Hide Dependencies',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -104,7 +107,6 @@ export const ja: LanguageTranslation = {
last_saved: '最後に保存された',
saved: '保存されました',
diagrams: 'ダイアグラム',
loading_diagram: 'ダイアグラムを読み込み中...',
deselect_all: 'すべての選択を解除',
select_all: 'すべてを選択',

View File

@@ -30,6 +30,9 @@ export const ko_KR: LanguageTranslation = {
theme: '테마',
show_dependencies: '종속성 보이기',
hide_dependencies: '종속성 숨기기',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: '공유',
@@ -101,7 +104,6 @@ export const ko_KR: LanguageTranslation = {
last_saved: '최근 저장일시: ',
saved: '저장됨',
diagrams: '다이어그램',
loading_diagram: '다이어그램 로딩중...',
deselect_all: '모두 선택 해제',
select_all: '모두 선택',

View File

@@ -30,6 +30,9 @@ export const mr: LanguageTranslation = {
theme: 'थीम',
show_dependencies: 'डिपेंडेन्सि दाखवा',
hide_dependencies: 'डिपेंडेन्सि लपवा',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
// TODO: Add translations
@@ -102,7 +105,6 @@ export const mr: LanguageTranslation = {
last_saved: 'शेवटचे जतन केले',
saved: 'जतन केले',
diagrams: 'आरेख',
loading_diagram: 'आरेख लोड करत आहे...',
deselect_all: 'सर्व निवड रद्द करा',
select_all: 'सर्व निवडा',

View File

@@ -30,6 +30,9 @@ export const ne: LanguageTranslation = {
theme: 'थिम',
show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्',
hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: 'शेयर गर्नुहोस्',
@@ -101,7 +104,6 @@ export const ne: LanguageTranslation = {
last_saved: 'अन्तिम सुरक्षित',
saved: 'सुरक्षित',
diagrams: 'डायाग्रामहरू',
loading_diagram: 'डायाग्राम लोड हुँदैछ...',
deselect_all: 'सबै चयन हटाउनुहोस्',
select_all: 'सबै चयन गर्नुहोस्',

View File

@@ -30,6 +30,9 @@ export const pt_BR: LanguageTranslation = {
theme: 'Tema',
show_dependencies: 'Mostrar Dependências',
hide_dependencies: 'Ocultar Dependências',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -102,7 +105,6 @@ export const pt_BR: LanguageTranslation = {
last_saved: 'Última vez salvo',
saved: 'Salvo',
diagrams: 'Diagramas',
loading_diagram: 'Carregando diagrama...',
deselect_all: 'Desmarcar Todos',
select_all: 'Selecionar Todos',

View File

@@ -30,6 +30,9 @@ export const ru: LanguageTranslation = {
theme: 'Тема',
show_dependencies: 'Показать зависимости',
hide_dependencies: 'Скрыть зависимости',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: 'Поделиться',
@@ -102,7 +105,6 @@ export const ru: LanguageTranslation = {
last_saved: 'Последнее сохранение',
saved: 'Сохранено',
diagrams: 'Диаграммы',
loading_diagram: 'Загрузка диаграммы...',
deselect_all: 'Отменить выбор всех',
select_all: 'Выбрать все',

View File

@@ -30,6 +30,9 @@ export const te: LanguageTranslation = {
theme: 'థీమ్',
show_dependencies: 'ఆధారాలు చూపించండి',
hide_dependencies: 'ఆధారాలను దాచండి',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -102,7 +105,6 @@ export const te: LanguageTranslation = {
last_saved: 'చివరిగా సేవ్ చేయబడిన',
saved: 'సేవ్ చేయబడింది',
diagrams: 'చిత్రాలు',
loading_diagram: 'చిత్రం లోడ్ అవుతోంది...',
deselect_all: 'అన్ని ఎంచుకోకుండా ఉంచు',
select_all: 'అన్ని ఎంచుకోండి',

View File

@@ -30,6 +30,9 @@ export const tr: LanguageTranslation = {
theme: 'Tema',
show_dependencies: 'Bağımlılıkları Göster',
hide_dependencies: 'Bağımlılıkları Gizle',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -102,8 +105,6 @@ export const tr: LanguageTranslation = {
last_saved: 'Son kaydedilen',
saved: 'Kaydedildi',
diagrams: 'Diyagramlar',
loading_diagram: 'Diyagram yükleniyor...',
deselect_all: 'Hepsini Seçme',
select_all: 'Hepsini Seç',

View File

@@ -30,6 +30,9 @@ export const uk: LanguageTranslation = {
theme: 'Тема',
show_dependencies: 'Показати залежності',
hide_dependencies: 'Приховати залежності',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
// TODO: Translate
share: {
@@ -102,7 +105,6 @@ export const uk: LanguageTranslation = {
last_saved: 'Востаннє збережено',
saved: 'Збережено',
diagrams: 'Діаграми',
loading_diagram: 'Діаграма завантаження...',
deselect_all: 'Зняти вибір із усіх',
select_all: 'Вибрати усі',

View File

@@ -30,6 +30,9 @@ export const vi: LanguageTranslation = {
theme: 'Chủ đề',
show_dependencies: 'Hiển thị các phụ thuộc',
hide_dependencies: 'Ẩn các phụ thuộc',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: 'Chia sẻ',
@@ -101,7 +104,6 @@ export const vi: LanguageTranslation = {
last_saved: 'Đã lưu lần cuối',
saved: 'Đã lưu',
diagrams: 'Sơ đồ',
loading_diagram: 'Đang tải sơ đồ...',
deselect_all: 'Bỏ chọn tất cả',
select_all: 'Chọn tất cả',

View File

@@ -30,6 +30,9 @@ export const zh_CN: LanguageTranslation = {
theme: '主题',
show_dependencies: '展示依赖',
hide_dependencies: '隐藏依赖',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: '分享',
@@ -98,7 +101,6 @@ export const zh_CN: LanguageTranslation = {
last_saved: '上次保存时间:',
saved: '已保存',
diagrams: '关系图',
loading_diagram: '加载关系图...',
deselect_all: '取消全选',
select_all: '全选',
@@ -395,7 +397,7 @@ export const zh_CN: LanguageTranslation = {
};
export const zh_CNMetadata: LanguageMetadata = {
name: 'Chinese',
name: 'Chinese (Simplified)',
nativeName: '简体中文',
code: 'zh_CN',
};

View File

@@ -30,6 +30,9 @@ export const zh_TW: LanguageTranslation = {
theme: '主題',
show_dependencies: '顯示相依性',
hide_dependencies: '隱藏相依性',
// TODO: Translate
show_minimap: 'Show Mini Map',
hide_minimap: 'Hide Mini Map',
},
share: {
share: '分享',
@@ -98,7 +101,6 @@ export const zh_TW: LanguageTranslation = {
last_saved: '上次儲存於',
saved: '已儲存',
diagrams: '圖表',
loading_diagram: '正在載入圖表...',
deselect_all: '取消所有選取',
select_all: '全選',
@@ -395,6 +397,6 @@ export const zh_TW: LanguageTranslation = {
export const zh_TWMetadata: LanguageMetadata = {
nativeName: '繁體中文',
name: 'Traditional Chinese',
name: 'Chinese (Traditional)',
code: 'zh_TW',
};

View File

@@ -55,7 +55,6 @@ import {
TooltipTrigger,
TooltipContent,
} from '@/components/tooltip/tooltip';
import { useDialog } from '@/hooks/use-dialog';
import { MarkerDefinitions } from './marker-definitions';
import { CanvasContextMenu } from './canvas-context-menu';
import { areFieldTypesCompatible } from '@/lib/data/data-types/data-types';
@@ -76,6 +75,7 @@ import {
TOP_SOURCE_HANDLE_ID_PREFIX,
} from './table-node/table-node-dependency-indicator';
import { DatabaseType } from '@/lib/domain/database-type';
import { useAlert } from '@/context/alert-context/alert-context';
export type EdgeType = RelationshipEdgeType | DependencyEdgeType;
@@ -133,8 +133,9 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables, readonly }) => {
} = useChartDB();
const { showSidePanel } = useLayout();
const { effectiveTheme } = useTheme();
const { scrollAction, showDependenciesOnCanvas } = useLocalConfig();
const { showAlert } = useDialog();
const { scrollAction, showDependenciesOnCanvas, showMiniMapOnCanvas } =
useLocalConfig();
const { showAlert } = useAlert();
const { isMd: isDesktop } = useBreakpoint('md');
const nodeTypes = useMemo(() => ({ table: TableNode }), []);
const [highlightOverlappingTables, setHighlightOverlappingTables] =
@@ -871,12 +872,14 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables, readonly }) => {
>
<Toolbar readonly={readonly} />
</Controls>
<MiniMap
style={{
width: isDesktop ? 100 : 60,
height: isDesktop ? 100 : 60,
}}
/>
{showMiniMapOnCanvas && (
<MiniMap
style={{
width: isDesktop ? 100 : 60,
height: isDesktop ? 100 : 60,
}}
/>
)}
<Background
variant={BackgroundVariant.Dots}
gap={16}

View File

@@ -6,11 +6,15 @@ import {
useUpdateNodeInternals,
} from '@xyflow/react';
import { Button } from '@/components/button/button';
import { KeyRound, Trash2 } from 'lucide-react';
import { KeyRound, MessageCircleMore, Trash2 } from 'lucide-react';
import type { DBField } from '@/lib/domain/db-field';
import { useChartDB } from '@/hooks/use-chartdb';
import { cn } from '@/lib/utils';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/tooltip/tooltip';
export const LEFT_HANDLE_ID_PREFIX = 'left_rel_';
export const RIGHT_HANDLE_ID_PREFIX = 'right_rel_';
@@ -113,8 +117,27 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
/>
</>
)}
<div className="block truncate text-left">{field.name}</div>
<div className="flex max-w-[35%] justify-end gap-2 truncate hover:shrink-0">
<div
className={cn(
'flex items-center gap-1 truncate text-left',
{
'font-semibold': field.primaryKey || field.unique,
}
)}
>
<span className="truncate">{field.name}</span>
{field.comments ? (
<Tooltip>
<TooltipTrigger asChild>
<div className="shrink-0 cursor-pointer text-muted-foreground">
<MessageCircleMore size={14} />
</div>
</TooltipTrigger>
<TooltipContent>{field.comments}</TooltipContent>
</Tooltip>
) : null}
</div>
<div className="flex max-w-[35%] justify-end gap-1.5 truncate hover:shrink-0">
{field.primaryKey ? (
<div
className={cn(
@@ -128,11 +151,12 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
<div
className={cn(
'content-center truncate text-right text-xs text-muted-foreground',
'content-center truncate text-right text-xs text-muted-foreground shrink-0',
!readonly ? 'group-hover:hidden' : ''
)}
>
{field.type.name}
{field.nullable ? '?' : ''}
</div>
{readonly ? null : (
<div className="hidden flex-row group-hover:flex">

View File

@@ -36,10 +36,15 @@ import { KeyboardShortcutsProvider } from '@/context/keyboard-shortcuts-context/
import { Spinner } from '@/components/spinner/spinner';
import { Helmet } from 'react-helmet-async';
import { useStorage } from '@/hooks/use-storage';
import { AlertProvider } from '@/context/alert-context/alert-provider';
const OPEN_STAR_US_AFTER_SECONDS = 30;
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
const OPEN_BUCKLE_AFTER_SECONDS = 60;
const SHOW_BUCKLE_AGAIN_AFTER_DAYS = 1;
const SHOW_BUCKLE_AGAIN_OPENED_AFTER_DAYS = 7;
export const EditorDesktopLayoutLazy = React.lazy(
() => import('./editor-desktop-layout')
);
@@ -59,7 +64,8 @@ const EditorPageComponent: React.FC = () => {
const { openSelectSchema, showSidePanel } = useLayout();
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
const { showLoader, hideLoader } = useFullScreenLoader();
const { openCreateDiagramDialog, openStarUsDialog } = useDialog();
const { openCreateDiagramDialog, openStarUsDialog, openBuckleDialog } =
useDialog();
const { diagramId } = useParams<{ diagramId: string }>();
const { config, updateConfig } = useConfig();
const navigate = useNavigate();
@@ -71,6 +77,9 @@ const EditorPageComponent: React.FC = () => {
starUsDialogLastOpen,
setStarUsDialogLastOpen,
githubRepoOpened,
setBuckleDialogLastOpen,
buckleDialogLastOpen,
buckleWaitlistOpened,
} = useLocalConfig();
const { toast } = useToast();
const { t } = useTranslation();
@@ -163,6 +172,33 @@ const EditorPageComponent: React.FC = () => {
starUsDialogLastOpen,
]);
useEffect(() => {
if (!currentDiagram?.id) {
return;
}
if (
new Date().getTime() - buckleDialogLastOpen >
1000 *
60 *
60 *
24 *
(buckleWaitlistOpened
? SHOW_BUCKLE_AGAIN_OPENED_AFTER_DAYS
: SHOW_BUCKLE_AGAIN_AFTER_DAYS)
) {
const lastOpen = new Date().getTime();
setBuckleDialogLastOpen(lastOpen);
setTimeout(openBuckleDialog, OPEN_BUCKLE_AFTER_SECONDS * 1000);
}
}, [
currentDiagram?.id,
buckleWaitlistOpened,
openBuckleDialog,
setBuckleDialogLastOpen,
buckleDialogLastOpen,
]);
const lastDiagramId = useRef<string>('');
const handleChangeSchema = useCallback(async () => {
@@ -280,11 +316,13 @@ export const EditorPage: React.FC = () => (
<HistoryProvider>
<ReactFlowProvider>
<ExportImageProvider>
<DialogProvider>
<KeyboardShortcutsProvider>
<EditorPageComponent />
</KeyboardShortcutsProvider>
</DialogProvider>
<AlertProvider>
<DialogProvider>
<KeyboardShortcutsProvider>
<EditorPageComponent />
</KeyboardShortcutsProvider>
</DialogProvider>
</AlertProvider>
</ExportImageProvider>
</ReactFlowProvider>
</HistoryProvider>

View File

@@ -1,11 +1,9 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Label } from '@/components/label/label';
import { Button } from '@/components/button/button';
import { Check } from 'lucide-react';
import { Input } from '@/components/input/input';
import { useChartDB } from '@/hooks/use-chartdb';
import { useClickAway, useKeyPressEvent } from 'react-use';
import { useBreakpoint } from '@/hooks/use-breakpoint';
import { DiagramIcon } from '@/components/diagram-icon/diagram-icon';
import { useTranslation } from 'react-i18next';
import { cn } from '@/lib/utils';
@@ -22,7 +20,6 @@ export const DiagramName: React.FC<DiagramNameProps> = () => {
const { diagramName, updateDiagramName, currentDiagram } = useChartDB();
const { t } = useTranslation();
const { isMd: isDesktop } = useBreakpoint('md');
const [editMode, setEditMode] = useState(false);
const [editedDiagramName, setEditedDiagramName] =
React.useState(diagramName);
@@ -54,7 +51,7 @@ export const DiagramName: React.FC<DiagramNameProps> = () => {
<div className="group">
<div
className={cn(
'flex flex-1 flex-row items-center justify-center px-2 py-1',
'flex flex-1 flex-row items-center justify-center px-2 py-1 whitespace-nowrap',
{
'text-editable': !editMode,
}
@@ -64,9 +61,6 @@ export const DiagramName: React.FC<DiagramNameProps> = () => {
databaseType={currentDiagram.databaseType}
databaseEdition={currentDiagram.databaseEdition}
/>
<div className="flex">
{isDesktop ? <Label>{t('diagrams')}/</Label> : null}
</div>
<div className="flex flex-row items-center gap-1">
{editMode ? (
<>

View File

@@ -7,10 +7,11 @@ import {
TooltipContent,
TooltipTrigger,
} from '@/components/tooltip/tooltip';
import { useBreakpoint } from '@/hooks/use-breakpoint';
import { useTranslation } from 'react-i18next';
import type { LocaleFunc } from 'timeago.js';
import { register as registerLocale } from 'timeago.js';
import { Save } from 'lucide-react';
export interface LastSavedProps {}
const timeAgolocaleFromLanguage = async (
@@ -73,8 +74,7 @@ const timeAgolocaleFromLanguage = async (
export const LastSaved: React.FC<LastSavedProps> = () => {
const { currentDiagram } = useChartDB();
const { t, i18n } = useTranslation();
const { isMd: isDesktop } = useBreakpoint('md');
const { i18n } = useTranslation();
const [language, setLanguage] = useState<string>('en_US');
useEffect(() => {
@@ -93,8 +93,11 @@ export const LastSaved: React.FC<LastSavedProps> = () => {
return (
<Tooltip>
<TooltipTrigger>
<Badge variant="secondary" className="flex gap-1">
{isDesktop ? t('last_saved') : t('saved')}
<Badge
variant="secondary"
className="flex gap-1.5 whitespace-nowrap"
>
<Save size={16} />
<TimeAgo
datetime={currentDiagram.updatedAt}
locale={language}

View File

@@ -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<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,
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: (
<div className="flex flex-col gap-1 text-sm">
<div>
We set a budget to allow the community to
check the feature. You have reached the
limit of 5 AI exports every 30min.
</div>
<div>
Feel free to use your OPENAI_TOKEN, see the
manual{' '}
<a
href="https://github.com/chartdb/chartdb"
target="_blank"
className="text-pink-600 hover:underline"
rel="noreferrer"
>
here.
</a>
</div>
</div>
),
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 (
<Menubar className="h-8 border-none py-2 shadow-none md:h-10 md:py-0">
<MenubarMenu>
<MenubarTrigger>{t('menu.file.file')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={createNewDiagram}>
{t('menu.file.new')}
</MenubarItem>
<MenubarItem onClick={openDiagram}>
{t('menu.file.open')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.OPEN_DIAGRAM
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarItem onClick={updateDiagramUpdatedAt}>
{t('menu.file.save')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.SAVE_DIAGRAM
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.file.import_database')}
</MenubarSubTrigger>
<MenubarSubContent>
<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>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.file.export_sql')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem
onClick={() => exportSQL(DatabaseType.GENERIC)}
>
{databaseTypeToLabelMap['generic']}
</MenubarItem>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.POSTGRESQL)
}
>
{databaseTypeToLabelMap['postgresql']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() => exportSQL(DatabaseType.MYSQL)}
>
{databaseTypeToLabelMap['mysql']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.SQL_SERVER)
}
>
{databaseTypeToLabelMap['sql_server']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() => exportSQL(DatabaseType.MARIADB)}
>
{databaseTypeToLabelMap['mariadb']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() => exportSQL(DatabaseType.SQLITE)}
>
{databaseTypeToLabelMap['sqlite']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSub>
<MenubarSubTrigger>
{t('menu.file.export_as')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem onClick={exportPNG}>PNG</MenubarItem>
<MenubarItem onClick={exportJPG}>JPG</MenubarItem>
<MenubarItem onClick={exportSVG}>SVG</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.file.delete_diagram')}
</MenubarItem>
<MenubarSeparator />
<MenubarItem>{t('menu.file.exit')}</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>
{databaseType !== DatabaseType.CLICKHOUSE ? (
<MenubarItem onClick={showOrHideDependencies}>
{showDependenciesOnCanvas
? t('menu.view.hide_dependencies')
: t('menu.view.show_dependencies')}
</MenubarItem>
) : null}
<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.theme')}
</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.share.share')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={openExportDiagramDialog}>
{t('menu.share.export_diagram')}
</MenubarItem>
<MenubarItem onClick={openImportDiagramDialog}>
{t('menu.share.import_diagram')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{t('menu.help.help')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={openChartDBIO}>
{t('menu.help.visit_website')}
</MenubarItem>
<MenubarItem onClick={openJoinDiscord}>
{t('menu.help.join_discord')}
</MenubarItem>
<MenubarItem onClick={openCalendly}>
{t('menu.help.schedule_a_call')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
);
};

View File

@@ -1,177 +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 { Menu } from './menu/menu';
export interface TopNavbarProps {}
export const TopNavbar: React.FC<TopNavbarProps> = () => {
const {
clearDiagramData,
deleteDiagram,
updateDiagramUpdatedAt,
databaseType,
} = useChartDB();
const {
openCreateDiagramDialog,
openOpenDiagramDialog,
openExportSQLDialog,
openImportDatabaseDialog,
showAlert,
openExportImageDialog,
openExportDiagramDialog,
openImportDiagramDialog,
} = useDialog();
const { setTheme, theme } = useTheme();
const { hideSidePanel, isSidePanelShowed, showSidePanel } = useLayout();
const {
scrollAction,
setScrollAction,
setShowCardinality,
showCardinality,
setShowDependenciesOnCanvas,
showDependenciesOnCanvas,
} = 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: (
<div className="flex flex-col gap-1 text-sm">
<div>
We set a budget to allow the community to
check the feature. You have reached the
limit of 5 AI exports every 30min.
</div>
<div>
Feel free to use your OPENAI_TOKEN, see the
manual{' '}
<a
href="https://github.com/chartdb/chartdb"
target="_blank"
className="text-pink-600 hover:underline"
rel="noreferrer"
>
here.
</a>
</div>
</div>
),
closeLabel: 'Close',
});
return;
}
updateConfig({
exportActions: [...lastExportsInLastHalfHour, now],
});
}
openExportSQLDialog({
targetDatabaseType: databaseType,
});
},
[config?.exportActions, updateConfig, showAlert, openExportSQLDialog]
);
const renderStars = useCallback(() => {
return (
@@ -184,27 +25,26 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
);
}, [isDesktop]);
const showOrHideSidePanel = useCallback(() => {
if (isSidePanelShowed) {
hideSidePanel();
} else {
showSidePanel();
}
}, [isSidePanelShowed, showSidePanel, hideSidePanel]);
const openBuckleWaitlist = useCallback(() => {
window.open('https://waitlist.buckle.dev', '_blank');
}, []);
const showOrHideCardinality = useCallback(() => {
setShowCardinality(!showCardinality);
}, [showCardinality, setShowCardinality]);
const showOrHideDependencies = useCallback(() => {
setShowDependenciesOnCanvas(!showDependenciesOnCanvas);
}, [showDependenciesOnCanvas, setShowDependenciesOnCanvas]);
const emojiAI = '✨';
const renderGetBuckleButton = useCallback(() => {
return (
<button
className="gradient-background relative inline-flex items-center justify-center overflow-hidden rounded-lg p-0.5 text-base text-gray-700 focus:outline-none focus:ring-0"
onClick={openBuckleWaitlist}
>
<span className="relative inline-flex items-center justify-center whitespace-nowrap rounded-md bg-background px-2 py-0.5 font-primary text-xs font-semibold text-foreground md:text-sm">
ChartDB v2.0 🔥
</span>
</button>
);
}, [openBuckleWaitlist]);
return (
<nav className="flex flex-col justify-between border-b px-3 md:h-12 md:flex-row md:items-center md:px-4">
<div className="flex flex-1 flex-col justify-between gap-x-3 md:flex-row md:justify-normal">
<div className="flex flex-1 flex-col justify-between gap-x-1 md:flex-row md:justify-normal">
<div className="flex items-center justify-between pt-[8px] font-primary md:py-[10px]">
<a
href="https://chartdb.io"
@@ -223,357 +63,19 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
</a>
{!isDesktop ? (
<div className="flex items-center gap-2">
{renderGetBuckleButton()}
{renderStars()}
<LanguageNav />
</div>
) : null}
</div>
<Menubar className="h-8 border-none py-2 shadow-none md:h-10 md:py-0">
<MenubarMenu>
<MenubarTrigger>{t('menu.file.file')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={createNewDiagram}>
{t('menu.file.new')}
</MenubarItem>
<MenubarItem onClick={openDiagram}>
{t('menu.file.open')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.OPEN_DIAGRAM
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarItem onClick={updateDiagramUpdatedAt}>
{t('menu.file.save')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
KeyboardShortcutAction.SAVE_DIAGRAM
].keyCombinationLabel
}
</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.file.import_database')}
</MenubarSubTrigger>
<MenubarSubContent>
<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>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.file.export_sql')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.GENERIC)
}
>
{databaseTypeToLabelMap['generic']}
</MenubarItem>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.POSTGRESQL)
}
>
{databaseTypeToLabelMap['postgresql']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.MYSQL)
}
>
{databaseTypeToLabelMap['mysql']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.SQL_SERVER)
}
>
{databaseTypeToLabelMap['sql_server']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.MARIADB)
}
>
{databaseTypeToLabelMap['mariadb']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onClick={() =>
exportSQL(DatabaseType.SQLITE)
}
>
{databaseTypeToLabelMap['sqlite']}
<MenubarShortcut className="text-base">
{emojiAI}
</MenubarShortcut>
</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSub>
<MenubarSubTrigger>
{t('menu.file.export_as')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem onClick={exportPNG}>
PNG
</MenubarItem>
<MenubarItem onClick={exportJPG}>
JPG
</MenubarItem>
<MenubarItem onClick={exportSVG}>
SVG
</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.file.delete_diagram')}
</MenubarItem>
<MenubarSeparator />
<MenubarItem>{t('menu.file.exit')}</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>
{databaseType !== DatabaseType.CLICKHOUSE ? (
<MenubarItem onClick={showOrHideDependencies}>
{showDependenciesOnCanvas
? t('menu.view.hide_dependencies')
: t('menu.view.show_dependencies')}
</MenubarItem>
) : null}
<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.theme')}
</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.share.share')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={openExportDiagramDialog}>
{t('menu.share.export_diagram')}
</MenubarItem>
<MenubarItem onClick={openImportDiagramDialog}>
{t('menu.share.import_diagram')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{t('menu.help.help')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={openChartDBIO}>
{t('menu.help.visit_website')}
</MenubarItem>
<MenubarItem onClick={openJoinDiscord}>
{t('menu.help.join_discord')}
</MenubarItem>
<MenubarItem onClick={openCalendly}>
{t('menu.help.schedule_a_call')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
<Menu />
</div>
{isDesktop ? (
<>
<DiagramName />
<div className="hidden flex-1 items-center justify-end gap-2 sm:flex">
{renderGetBuckleButton()}
<LastSaved />
{renderStars()}
<LanguageNav />