mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-30 19:43:54 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74c1730425 | ||
|
|
94bed7fcce | ||
|
|
8abf2a7bfc | ||
|
|
ee659eaa03 | ||
|
|
7c5db0848e | ||
|
|
4b43f720e9 | ||
|
|
766b5164b8 | ||
|
|
7868ca9f42 | ||
|
|
0411742864 | ||
|
|
9831ac5a10 | ||
|
|
91c6fb9249 | ||
|
|
c155013668 | ||
|
|
1b0f293c87 | ||
|
|
df2dc03aa0 | ||
|
|
205d431c89 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -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
78
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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
BIN
public/buckle-animated.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 404 KiB |
BIN
public/buckle.png
Normal file
BIN
public/buckle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
15
src/context/alert-context/alert-context.tsx
Normal file
15
src/context/alert-context/alert-context.tsx
Normal 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);
|
||||
36
src/context/alert-context/alert-provider.tsx
Normal file
36
src/context/alert-context/alert-provider.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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?.();
|
||||
|
||||
80
src/dialogs/buckle-dialog/buckle-dialog.tsx
Normal file
80
src/dialogs/buckle-dialog/buckle-dialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
407
src/i18n/locales/ar.ts
Normal 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',
|
||||
};
|
||||
@@ -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: 'সব নির্বাচন করুন',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'બધા પસંદ કરો',
|
||||
|
||||
@@ -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: 'सभी को चुनें',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'すべてを選択',
|
||||
|
||||
@@ -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: '모두 선택',
|
||||
|
||||
@@ -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: 'सर्व निवडा',
|
||||
|
||||
@@ -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: 'सबै चयन गर्नुहोस्',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'Выбрать все',
|
||||
|
||||
@@ -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: 'అన్ని ఎంచుకోండి',
|
||||
|
||||
@@ -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ç',
|
||||
|
||||
@@ -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: 'Вибрати усі',
|
||||
|
||||
@@ -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ả',
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ? (
|
||||
<>
|
||||
|
||||
@@ -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}
|
||||
|
||||
512
src/pages/editor-page/top-navbar/menu/menu.tsx
Normal file
512
src/pages/editor-page/top-navbar/menu/menu.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user