Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b56b04925c | ||
|
|
635fb53c9f | ||
|
|
d6659795bc | ||
|
|
348f80568e | ||
|
|
5f9c74a9ad | ||
|
|
5409288388 | ||
|
|
2309306ef5 | ||
|
|
3574cecc7c | ||
|
|
29b8edc051 | ||
|
|
5fc10a7e64 | ||
|
|
807cd22e0c | ||
|
|
03772f6b4f | ||
|
|
885eb719de | ||
|
|
94656ec7a5 | ||
|
|
a0e966b64f | ||
|
|
a8fe491c1b | ||
|
|
ddeef3b134 | ||
|
|
d45677e92d | ||
|
|
9c7d03c285 | ||
|
|
be1b109f23 | ||
|
|
05eaf85a3d |
@@ -1,29 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:css-modules/recommended',
|
||||
'plugin:tailwindcss/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
// 'plugin:jsx-a11y/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh', 'css-modules', 'tailwindcss', 'jsx-a11y'],
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
'react/no-unescaped-entities': 'off',
|
||||
'react/prop-types': 'off',
|
||||
},
|
||||
settings: {
|
||||
react: { version: 'detect' },
|
||||
},
|
||||
};
|
||||
36
CHANGELOG.md
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [1.5.0](https://github.com/chartdb/chartdb/compare/v1.4.0...v1.5.0) (2024-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **CockroachDB:** Add CockroachDB support ([#472](https://github.com/chartdb/chartdb/issues/472)) ([5409288](https://github.com/chartdb/chartdb/commit/54092883883b135f6ace51d86754b1df76603d30))
|
||||
* **i18n:** translate share and dialog sections in Indonesian locale files ([#468](https://github.com/chartdb/chartdb/issues/468)) ([3574cec](https://github.com/chartdb/chartdb/commit/3574cecc7c73dcab404b82115d20e1ad0cd26b37))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** fix update diagram id ([#477](https://github.com/chartdb/chartdb/issues/477)) ([348f805](https://github.com/chartdb/chartdb/commit/348f80568e0f686ee478147fdc43a5d43b5c1ebb))
|
||||
* **dialogs:** fix footer position on dialogs ([#470](https://github.com/chartdb/chartdb/issues/470)) ([2309306](https://github.com/chartdb/chartdb/commit/2309306ef590783b00a2489209092107dd9a3788))
|
||||
* **sql-server import:** nullable should be boolean instead of string ([#480](https://github.com/chartdb/chartdb/issues/480)) ([635fb53](https://github.com/chartdb/chartdb/commit/635fb53c9f7ebd1e5ef4d9274af041edc08f04c3))
|
||||
|
||||
## [1.4.0](https://github.com/chartdb/chartdb/compare/v1.3.1...v1.4.0) (2024-12-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **add templates:** add six more templates ([#452](https://github.com/chartdb/chartdb/issues/452)) ([be1b109](https://github.com/chartdb/chartdb/commit/be1b109f23e62df4cc63fa8914c2754f7809cc08))
|
||||
* **add templates:** add six more templates (django-axes, laravel-activitylog, octobox, pay-rails, pixelfed, polr) ([#460](https://github.com/chartdb/chartdb/issues/460)) ([03772f6](https://github.com/chartdb/chartdb/commit/03772f6b4f99f9c4350356aa0f2a4666f4f1794d))
|
||||
* **add templates:** add six more templates (reversion, screeenly, staytus, deployer, devise, talk) ([#457](https://github.com/chartdb/chartdb/issues/457)) ([ddeef3b](https://github.com/chartdb/chartdb/commit/ddeef3b134efa893e1c1e15e2f87c27157200e2d))
|
||||
* **clickhouse:** add ClickHouse support ([#463](https://github.com/chartdb/chartdb/issues/463)) ([807cd22](https://github.com/chartdb/chartdb/commit/807cd22e0c739f339fa07fe1d2f043c5411ae41f))
|
||||
* **i18n:** Added bangla translations ([#432](https://github.com/chartdb/chartdb/issues/432)) ([885eb71](https://github.com/chartdb/chartdb/commit/885eb719de577c2652fbed1ed287f38fcc98c148))
|
||||
* **side-panel:** Add functionality of order tables by drag & drop ([#425](https://github.com/chartdb/chartdb/issues/425)) ([a0e966b](https://github.com/chartdb/chartdb/commit/a0e966b64f8070d4595d47b2fb39e8bbf427b794))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **clipboard:** defensive for navigator clipboard ([#462](https://github.com/chartdb/chartdb/issues/462)) ([5fc10a7](https://github.com/chartdb/chartdb/commit/5fc10a7e649fc5877bb297b519b1b6a8b81f1323))
|
||||
* **import-database:** update database type after importing into an existing generic diagra ([#456](https://github.com/chartdb/chartdb/issues/456)) ([a8fe491](https://github.com/chartdb/chartdb/commit/a8fe491c1b5a30d9f4144cefa9111dd3dfd5df1a))
|
||||
* **Last Saved:** Translate the "last saved" relative date message ([#400](https://github.com/chartdb/chartdb/issues/400)) ([d45677e](https://github.com/chartdb/chartdb/commit/d45677e92d72efc6cea8f865ce46f0be6ec9961f))
|
||||
* **mariadb-types:** Add uuid data type ([#459](https://github.com/chartdb/chartdb/issues/459)) ([94656ec](https://github.com/chartdb/chartdb/commit/94656ec7a5435c2da262fb3bc6a6d381d554b0c1))
|
||||
* window type ([#454](https://github.com/chartdb/chartdb/issues/454)) ([9c7d03c](https://github.com/chartdb/chartdb/commit/9c7d03c285ff6f818eef3199c9b7a530d03a1fec))
|
||||
|
||||
## [1.3.1](https://github.com/chartdb/chartdb/compare/v1.3.0...v1.3.1) (2024-11-26)
|
||||
|
||||
|
||||
|
||||
@@ -95,15 +95,15 @@ npm install
|
||||
VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
|
||||
```
|
||||
|
||||
### Running the Docker Container
|
||||
### Run the Docker Container
|
||||
```bash
|
||||
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 ghcr.io/chartdb/chartdb:latest
|
||||
```
|
||||
|
||||
#### Build & run Docker image locally
|
||||
#### Build and Run locally
|
||||
```bash
|
||||
docker build -t chartdb . (If you want AI capabilities, use `docker build --build-arg VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -t chartdb .`)
|
||||
docker run -p 8080:80 chartdb
|
||||
docker build -t chartdb .
|
||||
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 chartdb
|
||||
```
|
||||
|
||||
Open your browser and navigate to `http://localhost:8080`.
|
||||
|
||||
77
eslint.config.mjs
Normal file
@@ -0,0 +1,77 @@
|
||||
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import cssModules from 'eslint-plugin-css-modules';
|
||||
import tailwindcss from 'eslint-plugin-tailwindcss';
|
||||
import jsxA11Y from 'eslint-plugin-jsx-a11y';
|
||||
import globals from 'globals';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import js from '@eslint/js';
|
||||
import { FlatCompat } from '@eslint/eslintrc';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist', '**/.eslintrc.cjs', 'tailwind.config.js'],
|
||||
// files: ['**/*.ts', '**/*.tsx'],
|
||||
},
|
||||
...fixupConfigRules(
|
||||
compat.extends(
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:css-modules/recommended',
|
||||
'plugin:tailwindcss/recommended',
|
||||
'plugin:prettier/recommended'
|
||||
)
|
||||
),
|
||||
{
|
||||
plugins: {
|
||||
'react-refresh': reactRefresh,
|
||||
'css-modules': fixupPluginRules(cssModules),
|
||||
tailwindcss: fixupPluginRules(tailwindcss),
|
||||
'jsx-a11y': jsxA11Y,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
// parserOptions: {
|
||||
// project: './tsconfig.json',
|
||||
// },
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{
|
||||
allowConstantExport: true,
|
||||
},
|
||||
],
|
||||
|
||||
'react/no-unescaped-entities': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
21082
package-lock.json
generated
16
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "chartdb",
|
||||
"private": true,
|
||||
"version": "1.3.1",
|
||||
"version": "1.5.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "npm run lint && tsc -b && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"preview": "vite preview",
|
||||
"prepare": "husky"
|
||||
@@ -69,22 +69,26 @@
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.4",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.16.0",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
||||
"@typescript-eslint/parser": "^7.15.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
||||
"@typescript-eslint/parser": "^8.18.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"eslint-plugin-tailwindcss": "^3.17.4",
|
||||
"globals": "^15.13.0",
|
||||
"husky": "^9.1.5",
|
||||
"postcss": "^8.4.40",
|
||||
"prettier": "^3.3.3",
|
||||
|
||||
BIN
src/assets/clickhouse_logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/clickhouse_logo_2.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/clickhouse_logo_dark.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
src/assets/cockroachdb_logo.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/cockroachdb_logo_2.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
src/assets/cockroachdb_logo_dark.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/templates/cachet-db-dark.png
Normal file
|
After Width: | Height: | Size: 447 KiB |
BIN
src/assets/templates/cachet-db.png
Normal file
|
After Width: | Height: | Size: 486 KiB |
BIN
src/assets/templates/canvas-db-dark.png
Normal file
|
After Width: | Height: | Size: 346 KiB |
BIN
src/assets/templates/canvas-db.png
Normal file
|
After Width: | Height: | Size: 379 KiB |
BIN
src/assets/templates/deployer-db-dark.png
Normal file
|
After Width: | Height: | Size: 424 KiB |
BIN
src/assets/templates/deployer-db.png
Normal file
|
After Width: | Height: | Size: 497 KiB |
BIN
src/assets/templates/devise-db-dark.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
src/assets/templates/devise-db.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
src/assets/templates/django-axes-db-dark.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
src/assets/templates/django-axes-db.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
src/assets/templates/doorkeeper-db-dark.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
src/assets/templates/doorkeeper-db.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
src/assets/templates/flipper-db-dark.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
src/assets/templates/flipper-db.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
src/assets/templates/laravel-activitylog-db-dark.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
src/assets/templates/laravel-activitylog-db.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
src/assets/templates/octobox-db-dark.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
src/assets/templates/octobox-db.png
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
src/assets/templates/orchid-db-dark.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
src/assets/templates/orchid-db.png
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
src/assets/templates/pay-rails-db-dark.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
src/assets/templates/pay-rails-db.png
Normal file
|
After Width: | Height: | Size: 371 KiB |
BIN
src/assets/templates/pixelfed-db-dark.png
Normal file
|
After Width: | Height: | Size: 593 KiB |
BIN
src/assets/templates/pixelfed-db.png
Normal file
|
After Width: | Height: | Size: 687 KiB |
BIN
src/assets/templates/polr-db-dark.png
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
src/assets/templates/polr-db.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
src/assets/templates/reversion-db-dark.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
src/assets/templates/reversion-db.png
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
src/assets/templates/screeenly-db-dark.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
src/assets/templates/screeenly-db.png
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
src/assets/templates/staytus-db-dark.png
Normal file
|
After Width: | Height: | Size: 424 KiB |
BIN
src/assets/templates/staytus-db.png
Normal file
|
After Width: | Height: | Size: 471 KiB |
BIN
src/assets/templates/taggit-db-dark.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
src/assets/templates/taggit-db.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
src/assets/templates/talk-db-dark.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
src/assets/templates/talk-db.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
@@ -3,6 +3,7 @@ import React, { lazy, Suspense, useCallback, useEffect } from 'react';
|
||||
import { Spinner } from '../spinner/spinner';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { useMonaco } from '@monaco-editor/react';
|
||||
import { useToast } from '@/components/toast/use-toast';
|
||||
import { Button } from '../button/button';
|
||||
import { Copy, CopyCheck } from 'lucide-react';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
||||
@@ -38,6 +39,7 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
const { t } = useTranslation();
|
||||
const monaco = useMonaco();
|
||||
const { effectiveTheme } = useTheme();
|
||||
const { toast } = useToast();
|
||||
const [isCopied, setIsCopied] = React.useState(false);
|
||||
const [tooltipOpen, setTooltipOpen] = React.useState(false);
|
||||
|
||||
@@ -66,10 +68,32 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
}
|
||||
}, [code, monaco, autoScroll]);
|
||||
|
||||
const copyToClipboard = useCallback(() => {
|
||||
navigator.clipboard.writeText(code);
|
||||
setIsCopied(true);
|
||||
}, [code]);
|
||||
const copyToClipboard = useCallback(async () => {
|
||||
if (!navigator?.clipboard) {
|
||||
toast({
|
||||
title: t('copy_to_clipboard_toast.unsupported.title'),
|
||||
variant: 'destructive',
|
||||
description: t(
|
||||
'copy_to_clipboard_toast.unsupported.description'
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
setIsCopied(true);
|
||||
} catch {
|
||||
setIsCopied(false);
|
||||
toast({
|
||||
title: t('copy_to_clipboard_toast.failed.title'),
|
||||
variant: 'destructive',
|
||||
description: t(
|
||||
'copy_to_clipboard_toast.failed.description'
|
||||
),
|
||||
});
|
||||
}
|
||||
}, [code, t, toast]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||
import {
|
||||
databaseEditionToImageMap,
|
||||
databaseEditionToLabelMap,
|
||||
@@ -9,39 +9,44 @@ import {
|
||||
databaseSecondaryLogoMap,
|
||||
databaseTypeToLabelMap,
|
||||
} from '@/lib/databases';
|
||||
import type { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface DiagramIconProps {
|
||||
diagram: Diagram;
|
||||
export interface DiagramIconProps
|
||||
extends React.ComponentPropsWithoutRef<'div'> {
|
||||
databaseType: DatabaseType;
|
||||
databaseEdition?: DatabaseEdition;
|
||||
imgClassName?: string;
|
||||
}
|
||||
|
||||
export const DiagramIcon = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipTrigger>,
|
||||
DiagramIconProps
|
||||
>(({ diagram }, ref) =>
|
||||
diagram.databaseEdition ? (
|
||||
>(({ databaseType, databaseEdition, className, imgClassName }, ref) =>
|
||||
databaseEdition ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="mr-1" ref={ref}>
|
||||
<TooltipTrigger className={cn('mr-1', className)} ref={ref} asChild>
|
||||
<img
|
||||
src={databaseEditionToImageMap[diagram.databaseEdition]}
|
||||
className="h-5 max-w-fit rounded-full"
|
||||
src={databaseEditionToImageMap[databaseEdition]}
|
||||
className={cn('h-5 max-w-fit rounded-full', imgClassName)}
|
||||
alt="database"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{databaseEditionToLabelMap[diagram.databaseEdition]}
|
||||
{databaseEditionToLabelMap[databaseEdition]}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="mr-2" ref={ref}>
|
||||
<TooltipTrigger className={cn('mr-2', className)} ref={ref} asChild>
|
||||
<img
|
||||
src={databaseSecondaryLogoMap[diagram.databaseType]}
|
||||
className="h-5 max-w-fit"
|
||||
src={databaseSecondaryLogoMap[databaseType]}
|
||||
className={cn('h-5 max-w-fit', imgClassName)}
|
||||
alt="database"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{databaseTypeToLabelMap[diagram.databaseType]}
|
||||
{databaseTypeToLabelMap[databaseType]}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
@@ -117,7 +117,10 @@ const DialogInternalContent = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ScrollArea
|
||||
ref={ref}
|
||||
className={cn('flex max-h-screen flex-col overflow-y-auto', className)}
|
||||
className={cn(
|
||||
'flex flex-1 max-h-screen flex-col overflow-y-auto',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -14,6 +14,7 @@ type ToasterToast = ToastProps & {
|
||||
layout?: 'row' | 'column';
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const actionTypes = {
|
||||
ADD_TOAST: 'ADD_TOAST',
|
||||
UPDATE_TOAST: 'UPDATE_TOAST',
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ChartDBProviderProps {
|
||||
diagram?: Diagram;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export const ChartDBProvider: React.FC<
|
||||
React.PropsWithChildren<ChartDBProviderProps>
|
||||
> = ({ children, diagram, readonly }) => {
|
||||
@@ -310,6 +311,7 @@ export const ChartDBProvider: React.FC<
|
||||
color: randomColor(),
|
||||
createdAt: Date.now(),
|
||||
isView: false,
|
||||
order: tables.length,
|
||||
...attributes,
|
||||
};
|
||||
await addTable(table);
|
||||
|
||||
@@ -122,6 +122,18 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
config: '++id, defaultDiagramId',
|
||||
});
|
||||
|
||||
db.version(8).stores({
|
||||
diagrams:
|
||||
'++id, name, databaseType, databaseEdition, createdAt, updatedAt',
|
||||
db_tables:
|
||||
'++id, diagramId, name, schema, x, y, fields, indexes, color, createdAt, width, comment, isView, isMaterializedView, order',
|
||||
db_relationships:
|
||||
'++id, diagramId, name, sourceSchema, sourceTableId, targetSchema, targetTableId, sourceFieldId, targetFieldId, type, createdAt',
|
||||
db_dependencies:
|
||||
'++id, diagramId, schema, tableId, dependentSchema, dependentTableId, createdAt',
|
||||
config: '++id, defaultDiagramId',
|
||||
});
|
||||
|
||||
db.on('ready', async () => {
|
||||
const config = await getConfig();
|
||||
|
||||
@@ -270,6 +282,23 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
attributes: Partial<Diagram>;
|
||||
}) => {
|
||||
await db.diagrams.update(id, attributes);
|
||||
|
||||
if (attributes.id) {
|
||||
await Promise.all([
|
||||
db.db_tables
|
||||
.where('diagramId')
|
||||
.equals(id)
|
||||
.modify({ diagramId: attributes.id }),
|
||||
db.db_relationships
|
||||
.where('diagramId')
|
||||
.equals(id)
|
||||
.modify({ diagramId: attributes.id }),
|
||||
db.db_dependencies
|
||||
.where('diagramId')
|
||||
.equals(id)
|
||||
.modify({ diagramId: attributes.id }),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDiagram: StorageContext['deleteDiagram'] = async (
|
||||
@@ -345,15 +374,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
.equals(diagramId)
|
||||
.toArray();
|
||||
|
||||
// Sort tables first alphabetically, then views alphabetically
|
||||
return tables.sort((a, b) => {
|
||||
if (a.isView === b.isView) {
|
||||
// Both are either tables or views, so sort alphabetically by name
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
// If one is a view and the other is not, put tables first
|
||||
return a.isView ? 1 : -1;
|
||||
});
|
||||
return tables;
|
||||
};
|
||||
|
||||
const addRelationship: StorageContext['addRelationship'] = async ({
|
||||
|
||||
@@ -8,10 +8,13 @@ export interface ExampleOptionProps {}
|
||||
export const ExampleOption: React.FC<ExampleOptionProps> = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Link href="/examples" className="text-primary hover:text-primary">
|
||||
<div className="flex size-20 cursor-pointer flex-col items-center rounded-md border py-3 text-center md:size-32">
|
||||
<div className="flex flex-1 items-center">
|
||||
<LayoutGrid size={34} />
|
||||
<Link
|
||||
href="/examples"
|
||||
className="col-span-3 text-primary hover:text-primary"
|
||||
>
|
||||
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 rounded-md border py-3 text-center">
|
||||
<div className="flex items-center">
|
||||
<LayoutGrid className="size-4" />
|
||||
</div>
|
||||
<div className="flex flex-col-reverse">
|
||||
<div className="hidden text-sm text-primary md:flex">
|
||||
|
||||
@@ -1,22 +1,61 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { ToggleGroup } from '@/components/toggle/toggle-group';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { DatabaseOption } from './database-option';
|
||||
import { ExampleOption } from './example-option';
|
||||
|
||||
import { Button } from '@/components/button/button';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
export interface SelectDatabaseContentProps {
|
||||
databaseType: DatabaseType;
|
||||
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
|
||||
onContinue: () => void;
|
||||
}
|
||||
|
||||
const ROW_SIZE = 3;
|
||||
const ROWS = 2;
|
||||
const TOTAL_SLOTS = ROW_SIZE * ROWS;
|
||||
const SUPPORTED_DB_TYPES: DatabaseType[] = [
|
||||
DatabaseType.MYSQL,
|
||||
DatabaseType.POSTGRESQL,
|
||||
DatabaseType.MARIADB,
|
||||
DatabaseType.SQLITE,
|
||||
DatabaseType.SQL_SERVER,
|
||||
DatabaseType.COCKROACHDB,
|
||||
DatabaseType.CLICKHOUSE,
|
||||
];
|
||||
|
||||
export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
|
||||
databaseType,
|
||||
setDatabaseType,
|
||||
onContinue,
|
||||
}) => {
|
||||
const [currentRow, setCurrentRow] = useState(0);
|
||||
const currentDatabasesTypes = useMemo(
|
||||
() =>
|
||||
SUPPORTED_DB_TYPES.slice(
|
||||
currentRow * ROW_SIZE,
|
||||
currentRow * ROW_SIZE + TOTAL_SLOTS
|
||||
),
|
||||
[currentRow]
|
||||
);
|
||||
|
||||
const hasNextRow = useMemo(
|
||||
() => (currentRow + 1) * ROW_SIZE < SUPPORTED_DB_TYPES.length,
|
||||
[currentRow]
|
||||
);
|
||||
|
||||
const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]);
|
||||
|
||||
const toggleRow = () => {
|
||||
if (currentRow === 0 && hasNextRow) {
|
||||
setCurrentRow(currentRow + 1);
|
||||
} else if (currentRow > 0) {
|
||||
setCurrentRow(currentRow - 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4">
|
||||
<ToggleGroup
|
||||
value={databaseType}
|
||||
onValueChange={(value: DatabaseType) => {
|
||||
@@ -30,12 +69,41 @@ export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
|
||||
type="single"
|
||||
className="grid grid-flow-row grid-cols-3 gap-6"
|
||||
>
|
||||
<DatabaseOption type={DatabaseType.MYSQL} />
|
||||
<DatabaseOption type={DatabaseType.POSTGRESQL} />
|
||||
<DatabaseOption type={DatabaseType.MARIADB} />
|
||||
<DatabaseOption type={DatabaseType.SQLITE} />
|
||||
<DatabaseOption type={DatabaseType.SQL_SERVER} />
|
||||
<ExampleOption />
|
||||
{Array.from({ length: TOTAL_SLOTS }).map((_, index) =>
|
||||
currentDatabasesTypes?.[index] ? (
|
||||
<DatabaseOption
|
||||
key={currentDatabasesTypes[index]}
|
||||
type={currentDatabasesTypes[index]}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
|
||||
<div className="col-span-3 flex flex-1 flex-col gap-1">
|
||||
{hasNextRow || hasPreviousRow ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={toggleRow}
|
||||
className="col-span-3 h-8"
|
||||
>
|
||||
{currentRow === 0 ? (
|
||||
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 py-3 text-center md:h-12">
|
||||
<ChevronDown className="mr-2 size-3.5" />
|
||||
<span className="text-xs">
|
||||
More Databases
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 py-3 text-center md:h-12">
|
||||
<ChevronUp className="mr-2 size-3.5" />
|
||||
<span className="text-xs">
|
||||
Primary Databases
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
<ExampleOption />
|
||||
</div>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
const script = await exportSQLScript();
|
||||
setScript(script);
|
||||
setIsScriptLoading(false);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Dialog, DialogContent } from '@/components/dialog/dialog';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import type { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ImportDatabase } from '../common/import-database/import-database';
|
||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||
@@ -30,6 +30,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
||||
addTables,
|
||||
addRelationships,
|
||||
diagramName,
|
||||
databaseType: currentDatabaseType,
|
||||
updateDatabaseType,
|
||||
} = useChartDB();
|
||||
const [scriptResult, setScriptResult] = useState('');
|
||||
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
||||
@@ -282,6 +284,10 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
||||
}),
|
||||
]);
|
||||
|
||||
if (currentDatabaseType === DatabaseType.GENERIC) {
|
||||
await updateDatabaseType(databaseType);
|
||||
}
|
||||
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => ({
|
||||
...node,
|
||||
@@ -297,6 +303,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
||||
closeImportDatabaseDialog();
|
||||
}, [
|
||||
databaseEdition,
|
||||
currentDatabaseType,
|
||||
updateDatabaseType,
|
||||
databaseType,
|
||||
scriptResult,
|
||||
tables,
|
||||
|
||||
@@ -137,7 +137,12 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
<TableCell className="table-cell">
|
||||
<div className="flex justify-center">
|
||||
<DiagramIcon
|
||||
diagram={diagram}
|
||||
databaseType={
|
||||
diagram.databaseType
|
||||
}
|
||||
databaseEdition={
|
||||
diagram.databaseEdition
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { mr, mrMetadata } from './locales/mr';
|
||||
import { tr, trMetadata } from './locales/tr';
|
||||
import { id_ID, id_IDMetadata } from './locales/id_ID';
|
||||
import { te, teMetadata } from './locales/te';
|
||||
import { bn, bnMetadata } from './locales/bn';
|
||||
import { gu, guMetadata } from './locales/gu';
|
||||
import { vi, viMetadata } from './locales/vi';
|
||||
|
||||
@@ -40,6 +41,7 @@ export const languages: LanguageMetadata[] = [
|
||||
trMetadata,
|
||||
id_IDMetadata,
|
||||
teMetadata,
|
||||
bnMetadata,
|
||||
guMetadata,
|
||||
viMetadata,
|
||||
];
|
||||
@@ -62,6 +64,7 @@ const resources = {
|
||||
tr,
|
||||
id_ID,
|
||||
te,
|
||||
bn,
|
||||
gu,
|
||||
vi,
|
||||
};
|
||||
|
||||
408
src/i18n/locales/bn.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const bn: 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: 'নির্ভরতাগুলি লুকান',
|
||||
},
|
||||
|
||||
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:
|
||||
'{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
|
||||
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: 'সংরক্ষিত',
|
||||
diagrams: 'ডায়াগ্রাম',
|
||||
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 সার্ভারে যান।',
|
||||
step_2: 'যদি আপনি "গ্রিডে ফলাফল" ব্যবহার করেন, তাহলে নন-XML ডেটার জন্য সর্বাধিক চরিত্রগুলি 9999999-এ সেট করুন।',
|
||||
},
|
||||
instructions_link: 'সাহায্যের প্রয়োজন? এখানে দেখুন',
|
||||
check_script_result: 'স্ক্রিপ্ট ফলাফল যাচাই করুন',
|
||||
},
|
||||
|
||||
cancel: 'বাতিল করুন',
|
||||
back: 'ফিরে যান',
|
||||
import_from_file: 'ফাইল থেকে আমদানি করুন',
|
||||
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}} এর জন্য AI SQL তৈরি হচ্ছে...',
|
||||
description: 'এতে ৩০ সেকেন্ড পর্যন্ত সময় লাগতে পারে।',
|
||||
},
|
||||
error: {
|
||||
message:
|
||||
'SQL স্ক্রিপ্ট তৈরি করার সময় একটি ত্রুটি ঘটেছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন বা <0>আমাদের সাথে যোগাযোগ করুন</0>।',
|
||||
description:
|
||||
'আপনার OPENAI_TOKEN ব্যবহার করার জন্য বিনামূল্যে অভিজ্ঞতা নিন, ম্যানুয়াল <0>এখানে দেখুন</0>।',
|
||||
},
|
||||
},
|
||||
|
||||
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:
|
||||
'ডায়াগ্রাম JSON অবৈধ। অনুগ্রহ করে JSON পরীক্ষা করুন এবং আবার চেষ্টা করুন। সাহায্যের প্রয়োজন? chartdb.io@gmail.com-এ যোগাযোগ করুন।',
|
||||
},
|
||||
},
|
||||
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 bnMetadata: LanguageMetadata = {
|
||||
name: 'Bengali',
|
||||
nativeName: 'বাংলা',
|
||||
code: 'bn',
|
||||
};
|
||||
@@ -78,6 +78,18 @@ export const de: LanguageTranslation = {
|
||||
none: 'Keine',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Kopieren fehlgeschlagen',
|
||||
description: 'Zwischenablage nicht unterstützt',
|
||||
},
|
||||
failed: {
|
||||
title: 'Kopieren fehlgeschlagen',
|
||||
description:
|
||||
'Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'System',
|
||||
light: 'Hell',
|
||||
|
||||
@@ -77,6 +77,17 @@ export const en = {
|
||||
none: 'none',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Copy failed',
|
||||
description: 'Clipboard not supported.',
|
||||
},
|
||||
failed: {
|
||||
title: 'Copy failed',
|
||||
description: 'Something went wrong. Please try again.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'System',
|
||||
light: 'Light',
|
||||
|
||||
@@ -69,6 +69,17 @@ export const es: LanguageTranslation = {
|
||||
cancel: 'Cancelar',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Copia fallida',
|
||||
description: 'Portapapeles no soportado',
|
||||
},
|
||||
failed: {
|
||||
title: 'Copia fallida',
|
||||
description: 'Algo salió mal. Por favor, inténtelo de nuevo.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Sistema',
|
||||
light: 'Claro',
|
||||
|
||||
@@ -68,6 +68,17 @@ export const fr: LanguageTranslation = {
|
||||
cancel: 'Annuler',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Échec de la copie',
|
||||
description: 'Presse-papiers non pris en charge',
|
||||
},
|
||||
failed: {
|
||||
title: 'Échec de la copie',
|
||||
description: 'Quelque chose a mal tourné. Veuillez réessayer.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Système',
|
||||
light: 'Clair',
|
||||
|
||||
@@ -78,6 +78,17 @@ export const gu: LanguageTranslation = {
|
||||
none: 'કઈ નહીં',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'નકલ નિષ્ફળ',
|
||||
description: 'ક્લિપબોર્ડ આધારિત નથી',
|
||||
},
|
||||
failed: {
|
||||
title: 'નકલ નિષ્ફળ',
|
||||
description: 'કંઈક ખોટું થયું છે. કૃપા કરીને ફરી પ્રયાસ કરો.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'સિસ્ટમ',
|
||||
light: 'હલકો',
|
||||
|
||||
@@ -78,6 +78,17 @@ export const hi: LanguageTranslation = {
|
||||
none: 'कोई नहीं',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'कॉपी असफल',
|
||||
description: 'क्लिपबोर्ड समर्थित नहीं है',
|
||||
},
|
||||
failed: {
|
||||
title: 'कॉपी असफल',
|
||||
description: 'कुछ गलत हो गया। कृपया पुनः प्रयास करें।',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'सिस्टम',
|
||||
light: 'हल्का',
|
||||
|
||||
@@ -31,11 +31,10 @@ export const id_ID: LanguageTranslation = {
|
||||
show_dependencies: 'Tampilkan Dependensi',
|
||||
hide_dependencies: 'Sembunyikan Dependensi',
|
||||
},
|
||||
// TODO: Translate
|
||||
share: {
|
||||
share: 'Share',
|
||||
export_diagram: 'Export Diagram',
|
||||
import_diagram: 'Import Diagram',
|
||||
share: 'Bagikan',
|
||||
export_diagram: 'Ekspor Diagram',
|
||||
import_diagram: 'Impor Diagram',
|
||||
},
|
||||
help: {
|
||||
help: 'Bantuan',
|
||||
@@ -78,6 +77,17 @@ export const id_ID: LanguageTranslation = {
|
||||
none: 'Tidak ada',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Gagal menyalin',
|
||||
description: 'Clipboard tidak didukung',
|
||||
},
|
||||
failed: {
|
||||
title: 'Gagal menyalin',
|
||||
description: 'Ada yang salah. Silakan coba lagi.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Sistem',
|
||||
light: 'Terang',
|
||||
@@ -335,30 +345,28 @@ export const id_ID: LanguageTranslation = {
|
||||
confirm: 'Tentu saja!',
|
||||
},
|
||||
|
||||
// TODO: Translate
|
||||
export_diagram_dialog: {
|
||||
title: 'Export Diagram',
|
||||
description: 'Choose the format for export:',
|
||||
title: 'Ekspor Diagram',
|
||||
description: 'Pilih format untuk ekspor:',
|
||||
format_json: 'JSON',
|
||||
cancel: 'Cancel',
|
||||
export: 'Export',
|
||||
cancel: 'Batal',
|
||||
export: 'Ekspor',
|
||||
error: {
|
||||
title: 'Error exporting diagram',
|
||||
title: 'Error ekspor diagram',
|
||||
description:
|
||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
||||
'Sesuatu yang salah. Butuh bantuan? chartdb.io@gmail.com',
|
||||
},
|
||||
},
|
||||
|
||||
// TODO: Translate
|
||||
import_diagram_dialog: {
|
||||
title: 'Import Diagram',
|
||||
description: 'Paste the diagram JSON below:',
|
||||
cancel: 'Cancel',
|
||||
import: 'Import',
|
||||
title: 'Impor Diagram',
|
||||
description: 'Tempel diagram JSON di bawah:',
|
||||
cancel: 'Batal',
|
||||
import: 'Impor',
|
||||
error: {
|
||||
title: 'Error importing diagram',
|
||||
title: 'Error impor diagram',
|
||||
description:
|
||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
||||
'Diagram JSON tidak valid. Silakan cek JSON dan coba lagi. Butuh bantuan? chartdb.io@gmail.com',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -377,16 +385,13 @@ export const id_ID: LanguageTranslation = {
|
||||
table_node_context_menu: {
|
||||
edit_table: 'Ubah Tabel',
|
||||
delete_table: 'Hapus Tabel',
|
||||
// TODO: Translate
|
||||
duplicate_table: 'Duplicate Table',
|
||||
duplicate_table: 'Duplikat Tabel',
|
||||
},
|
||||
|
||||
// TODO: Translate
|
||||
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
||||
snap_to_grid_tooltip: 'Snap ke Kisi (Tahan {{key}})',
|
||||
|
||||
// TODO: Translate
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Double-click to edit',
|
||||
double_click_to_edit: 'Klik ganda untuk mengedit',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
|
||||
@@ -79,6 +79,18 @@ export const ja: LanguageTranslation = {
|
||||
none: 'なし',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'コピー失敗',
|
||||
description: 'クリップボードがサポートされていません',
|
||||
},
|
||||
failed: {
|
||||
title: 'コピー失敗',
|
||||
description:
|
||||
'何かがうまくいきませんでした。もう一度お試しください。',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'システム',
|
||||
light: 'ライト',
|
||||
|
||||
@@ -77,6 +77,17 @@ export const ko_KR: LanguageTranslation = {
|
||||
none: '없음',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '복사 실패',
|
||||
description: '클립보드가 지원되지 않습니다"',
|
||||
},
|
||||
failed: {
|
||||
title: '복사 실패',
|
||||
description: '문제가 발생했습니다. 다시 시도해주세요.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: '시스템 설정에 따름',
|
||||
light: '밝게',
|
||||
|
||||
@@ -78,6 +78,17 @@ export const mr: LanguageTranslation = {
|
||||
none: 'काहीही नाही',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'कॉपी अयशस्वी',
|
||||
description: 'क्लिपबोर्ड समर्थित नाही',
|
||||
},
|
||||
failed: {
|
||||
title: 'कॉपी अयशस्वी',
|
||||
description: 'काहीतरी चूक झाली. कृपया पुन्हा प्रयत्न करा.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'सिस्टम',
|
||||
light: 'लाईट',
|
||||
|
||||
@@ -77,6 +77,17 @@ export const ne: LanguageTranslation = {
|
||||
none: 'कुनै पनि छैन',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'प्रतिलिपि असफल',
|
||||
description: 'क्लिपबोर्ड समर्थित छैन',
|
||||
},
|
||||
failed: {
|
||||
title: 'प्रतिलिपि असफल',
|
||||
description: 'केही गडबड भयो। कृपया फेरि प्रयास गर्नुहोस्।',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'सिस्टम',
|
||||
light: 'लाइट',
|
||||
|
||||
@@ -78,6 +78,17 @@ export const pt_BR: LanguageTranslation = {
|
||||
none: 'nenhum',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Falha na cópia',
|
||||
description: 'Área de transferência não suportada',
|
||||
},
|
||||
failed: {
|
||||
title: 'Falha na cópia',
|
||||
description: 'Algo deu errado. Por favor, tente novamente.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Sistema',
|
||||
light: 'Claro',
|
||||
|
||||
@@ -77,6 +77,18 @@ export const ru: LanguageTranslation = {
|
||||
none: 'никто',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Ошибка копирования',
|
||||
description: 'Буфер обмена не поддерживается',
|
||||
},
|
||||
failed: {
|
||||
title: 'Ошибка копирования',
|
||||
description:
|
||||
'Что-то пошло не так. Пожалуйста, попробуйте еще раз.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Системная',
|
||||
light: 'Светлая',
|
||||
|
||||
@@ -78,6 +78,17 @@ export const te: LanguageTranslation = {
|
||||
none: 'ఎదరికాదు',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'కాపీ విఫలమైంది',
|
||||
description: 'క్లిప్బోర్డ్ మద్దతు ఇవ్వదు',
|
||||
},
|
||||
failed: {
|
||||
title: 'కాపీ విఫలమైంది',
|
||||
description: 'ఏదో తప్పు జరిగింది. దయచేసి మళ్లీ ప్రయత్నించండి.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'సిస్టమ్',
|
||||
light: 'హালకా',
|
||||
|
||||
@@ -78,6 +78,17 @@ export const tr: LanguageTranslation = {
|
||||
none: 'yok',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Kopyalama başarısız',
|
||||
description: 'Panoya desteklenmiyor',
|
||||
},
|
||||
failed: {
|
||||
title: 'Kopyalama başarısız',
|
||||
description: 'Bir şeyler ters gitti. Lütfen tekrar deneyin.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Sistem',
|
||||
light: 'Açık',
|
||||
|
||||
@@ -78,6 +78,17 @@ export const uk: LanguageTranslation = {
|
||||
none: 'немає',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Помилка копіювання',
|
||||
description: 'Буфер обміну не підтримується',
|
||||
},
|
||||
failed: {
|
||||
title: 'Помилка копіювання',
|
||||
description: 'Щось пішло не так. Будь ласка, спробуйте ще раз.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'система',
|
||||
light: 'світлий',
|
||||
|
||||
@@ -77,6 +77,17 @@ export const vi: LanguageTranslation = {
|
||||
none: 'không có',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Sao chép thất bại',
|
||||
description: 'Không hỗ trợ bảng tạm',
|
||||
},
|
||||
failed: {
|
||||
title: 'Sao chép thất bại',
|
||||
description: 'Đã xảy ra lỗi. Vui lòng thử lại.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Hệ thống',
|
||||
light: 'Sáng',
|
||||
@@ -97,7 +108,7 @@ export const vi: LanguageTranslation = {
|
||||
clear: 'Xóa',
|
||||
show_more: 'Hiển thị thêm',
|
||||
show_less: 'Hiển thị ít hơn',
|
||||
copy_to_clipboard: 'Sao chép vào Clipboard',
|
||||
copy_to_clipboard: 'Sao chép vào bảng tạm',
|
||||
copied: 'Đã sao chép!',
|
||||
|
||||
side_panel: {
|
||||
|
||||
@@ -74,6 +74,17 @@ export const zh_CN: LanguageTranslation = {
|
||||
none: '无',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '复制失败',
|
||||
description: '不支持剪贴板',
|
||||
},
|
||||
failed: {
|
||||
title: '复制失败',
|
||||
description: '出现问题。请再试一次。',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: '系统',
|
||||
light: '浅色',
|
||||
|
||||
@@ -74,6 +74,17 @@ export const zh_TW: LanguageTranslation = {
|
||||
none: '無',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '複製失敗',
|
||||
description: '不支援剪貼簿',
|
||||
},
|
||||
failed: {
|
||||
title: '複製失敗',
|
||||
description: '出現問題。請再試一次。',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: '系統',
|
||||
light: '淺色',
|
||||
|
||||
117
src/lib/data/data-types/clickhouse-data-types.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import type { DataType } from './data-types';
|
||||
|
||||
export const clickhouseDataTypes: readonly DataType[] = [
|
||||
// Numeric Types
|
||||
{ name: 'uint8', id: 'uint8' },
|
||||
{ name: 'uint16', id: 'uint16' },
|
||||
{ name: 'uint32', id: 'uint32' },
|
||||
{ name: 'uint64', id: 'uint64' },
|
||||
{ name: 'uint128', id: 'uint128' },
|
||||
{ name: 'uint256', id: 'uint256' },
|
||||
{ name: 'int8', id: 'int8' },
|
||||
{ name: 'int16', id: 'int16' },
|
||||
{ name: 'int32', id: 'int32' },
|
||||
{ name: 'int64', id: 'int64' },
|
||||
{ name: 'int128', id: 'int128' },
|
||||
{ name: 'int256', id: 'int256' },
|
||||
{ name: 'float32', id: 'float32' },
|
||||
{ name: 'float64', id: 'float64' },
|
||||
{ name: 'tinyint', id: 'tinyint' },
|
||||
{ name: 'int1', id: 'int1' },
|
||||
{ name: 'byte', id: 'byte' },
|
||||
{ name: 'tinyint signed', id: 'tinyint_signed' },
|
||||
{ name: 'int1 signed', id: 'int1_signed' },
|
||||
{ name: 'smallint', id: 'smallint' },
|
||||
{ name: 'smallint signed', id: 'smallint_signed' },
|
||||
{ name: 'int', id: 'int' },
|
||||
{ name: 'integer', id: 'integer' },
|
||||
{ name: 'mediumint', id: 'mediumint' },
|
||||
{ name: 'mediumint signed', id: 'mediumint_signed' },
|
||||
{ name: 'int signed', id: 'int_signed' },
|
||||
{ name: 'integer signed', id: 'integer_signed' },
|
||||
{ name: 'bigint', id: 'bigint' },
|
||||
{ name: 'signed', id: 'signed' },
|
||||
{ name: 'bigint signed', id: 'bigint_signed' },
|
||||
{ name: 'time', id: 'time' },
|
||||
{ name: 'float', id: 'float' },
|
||||
{ name: 'double', id: 'double' },
|
||||
{ name: 'real', id: 'real' },
|
||||
{ name: 'single', id: 'single' },
|
||||
{ name: 'double precision', id: 'double_precision' },
|
||||
|
||||
// string Types
|
||||
{ name: 'longtext', id: 'longtext' },
|
||||
{ name: 'mediumtext', id: 'mediumtext' },
|
||||
{ name: 'tinytext', id: 'tinytext' },
|
||||
{ name: 'text', id: 'text' },
|
||||
{ name: 'longblob', id: 'longblob' },
|
||||
{ name: 'mediumblob', id: 'mediumblob' },
|
||||
{ name: 'tinyblob', id: 'tinyblob' },
|
||||
{ name: 'blob', id: 'blob' },
|
||||
{ name: 'varchar', id: 'varchar' },
|
||||
{ name: 'char', id: 'char' },
|
||||
{ name: 'char large object', id: 'char_large_object' },
|
||||
{ name: 'char varying', id: 'char_varying' },
|
||||
{ name: 'character large object', id: 'character_large_object' },
|
||||
{ name: 'character varying', id: 'character_varying' },
|
||||
{ name: 'nchar large object', id: 'nchar_large_object' },
|
||||
{ name: 'nchar varying', id: 'nchar_varying' },
|
||||
{
|
||||
name: 'national character large object',
|
||||
id: 'national_character_large_object',
|
||||
},
|
||||
{ name: 'national character varying', id: 'national_character_varying' },
|
||||
{ name: 'national char varying', id: 'national_char_varying' },
|
||||
{ name: 'national character', id: 'national_character' },
|
||||
{ name: 'national char', id: 'national_char' },
|
||||
{ name: 'binary large object', id: 'binary_large_object' },
|
||||
{ name: 'binary varying', id: 'binary_varying' },
|
||||
{ name: 'fixedstring', id: 'fixedstring' },
|
||||
{ name: 'string', id: 'string' },
|
||||
|
||||
// Date Types
|
||||
{ name: 'date', id: 'date' },
|
||||
{ name: 'date32', id: 'date32' },
|
||||
{ name: 'datetime', id: 'datetime' },
|
||||
{ name: 'datetime64', id: 'datetime64' },
|
||||
|
||||
// JSON Types
|
||||
{ name: 'object', id: 'object' },
|
||||
{ name: 'json', id: 'json' },
|
||||
|
||||
// UUID Type
|
||||
{ name: 'uuid', id: 'uuid' },
|
||||
|
||||
// Boolean Type
|
||||
{ name: 'boolean', id: 'boolean' },
|
||||
|
||||
// Enum Type
|
||||
{ name: 'enum', id: 'enum' },
|
||||
{ name: 'lowcardinality', id: 'lowcardinality' },
|
||||
|
||||
// Array Type
|
||||
{ name: 'array', id: 'array' },
|
||||
|
||||
// Tuple Type
|
||||
{ name: 'tuple', id: 'tuple' },
|
||||
{ name: 'map', id: 'map' },
|
||||
|
||||
{ name: 'simpleaggregatefunction', id: 'simpleaggregatefunction' },
|
||||
{ name: 'aggregatefunction', id: 'aggregatefunction' },
|
||||
|
||||
{ name: 'nested', id: 'nested' },
|
||||
|
||||
{ name: 'ipv4', id: 'ipv4' },
|
||||
{ name: 'ipv6', id: 'ipv6' },
|
||||
|
||||
// Geography Types
|
||||
{ name: 'point', id: 'point' },
|
||||
{ name: 'ring', id: 'ring' },
|
||||
{ name: 'polygon', id: 'polygon' },
|
||||
{ name: 'multipolygon', id: 'multipolygon' },
|
||||
|
||||
{ name: 'expression', id: 'expression' },
|
||||
{ name: 'set', id: 'set' },
|
||||
{ name: 'nothing', id: 'nothing' },
|
||||
{ name: 'interval', id: 'interval' },
|
||||
] as const;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
import { DatabaseType } from '../../domain/database-type';
|
||||
import { clickhouseDataTypes } from './clickhouse-data-types';
|
||||
import { genericDataTypes } from './generic-data-types';
|
||||
import { mariadbDataTypes } from './mariadb-data-types';
|
||||
import { mysqlDataTypes } from './mysql-data-types';
|
||||
@@ -24,6 +25,8 @@ export const dataTypeMap: Record<DatabaseType, readonly DataType[]> = {
|
||||
[DatabaseType.SQL_SERVER]: sqlServerDataTypes,
|
||||
[DatabaseType.MARIADB]: mariadbDataTypes,
|
||||
[DatabaseType.SQLITE]: sqliteDataTypes,
|
||||
[DatabaseType.CLICKHOUSE]: clickhouseDataTypes,
|
||||
[DatabaseType.COCKROACHDB]: postgresDataTypes,
|
||||
} as const;
|
||||
|
||||
const compatibleTypes: Record<DatabaseType, Record<string, string[]>> = {
|
||||
@@ -39,6 +42,8 @@ const compatibleTypes: Record<DatabaseType, Record<string, string[]>> = {
|
||||
[DatabaseType.SQL_SERVER]: {},
|
||||
[DatabaseType.MARIADB]: {},
|
||||
[DatabaseType.SQLITE]: {},
|
||||
[DatabaseType.CLICKHOUSE]: {},
|
||||
[DatabaseType.COCKROACHDB]: {},
|
||||
[DatabaseType.GENERIC]: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -50,4 +50,5 @@ export const mariadbDataTypes: readonly DataType[] = [
|
||||
|
||||
// JSON Type
|
||||
{ name: 'json', id: 'json' },
|
||||
{ name: 'uuid', id: 'uuid' },
|
||||
] as const;
|
||||
|
||||
@@ -3,4 +3,5 @@ import { DatabaseType } from '../domain/database-type';
|
||||
export const defaultSchemas: { [key in DatabaseType]?: string } = {
|
||||
[DatabaseType.POSTGRESQL]: 'public',
|
||||
[DatabaseType.SQL_SERVER]: 'dbo',
|
||||
[DatabaseType.CLICKHOUSE]: 'default',
|
||||
};
|
||||
|
||||
@@ -381,6 +381,13 @@ const generateSQLPrompt = (databaseType: DatabaseType, sqlScript: string) => {
|
||||
- **Adding Foreign Keys to Existing Tables**: If adding a foreign key to an existing table is required, suggest creating a new table with the foreign key constraint, migrating the data, and renaming the new table to the original name.
|
||||
- **General SQLite Constraints**: Remember, \`ALTER TABLE\` in SQLite is limited and cannot add constraints after the table is created.
|
||||
- **Conditional Logic**: Ensure the script uses SQLite-compatible syntax and does not include unsupported features.
|
||||
`,
|
||||
clickhouse: '',
|
||||
cockroachdb: `
|
||||
- **Sequence Creation**: Use \`CREATE SEQUENCE IF NOT EXISTS\` for sequence creation.
|
||||
- **Table and Index Creation**: Use \`CREATE TABLE IF NOT EXISTS\` and \`CREATE INDEX IF NOT EXISTS\` to avoid errors if the object already exists.
|
||||
- **Serial and Identity Columns**: For auto-increment columns, use \`SERIAL\` or \`GENERATED BY DEFAULT AS IDENTITY\`.
|
||||
- **Conditional Statements**: Utilize PostgreSQL’s support for \`IF NOT EXISTS\` in relevant \`CREATE\` statements.
|
||||
`,
|
||||
};
|
||||
|
||||
|
||||
137
src/lib/data/import-metadata/scripts/clickhouse-script.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
export const clickhouseQuery = `WITH
|
||||
cols AS (
|
||||
SELECT arrayStringConcat(arrayMap(col_tuple ->
|
||||
concat('{"schema":"', col_tuple.1, '"',
|
||||
',"table":"', col_tuple.2, '"',
|
||||
',"name":"', col_tuple.3, '"',
|
||||
',"ordinal_position":"', toString(col_tuple.4), '"',
|
||||
',"type":"', col_tuple.5, '"',
|
||||
',"nullable":"', if(col_tuple.6 = 'NULLABLE', 'true', 'false'), '"',
|
||||
',"default":"', if(col_tuple.7 = '', 'null', col_tuple.7), '"',
|
||||
',"comment":', if(col_tuple.8 = '', '""', toString(toJSONString(col_tuple.8))), '}'),
|
||||
groupArray((
|
||||
col.database,
|
||||
col.table,
|
||||
col.name,
|
||||
col.position,
|
||||
col.type,
|
||||
col.default_kind,
|
||||
col.default_expression,
|
||||
col.comment
|
||||
))
|
||||
), ',') AS cols_metadata
|
||||
FROM system.columns AS col
|
||||
JOIN system.tables AS tbl
|
||||
ON col.database = tbl.database AND col.table = tbl.name
|
||||
WHERE lower(col.database) NOT IN ('system', 'information_schema')
|
||||
AND lower(col.table) NOT LIKE '.inner_id.%'
|
||||
AND tbl.is_temporary = 0 -- Exclude temporary tables if desired
|
||||
),
|
||||
tbl_sizes AS (
|
||||
SELECT database, table, sum(bytes_on_disk) AS size
|
||||
FROM system.parts
|
||||
GROUP BY database, table
|
||||
),
|
||||
tbls AS (
|
||||
SELECT arrayStringConcat(arrayMap(tbl_tuple ->
|
||||
concat('{"schema":"', tbl_tuple.1, '"',
|
||||
',"table":"', tbl_tuple.2, '"',
|
||||
',"rows":', toString(tbl_tuple.3),
|
||||
',"type":"', tbl_tuple.4, '"',
|
||||
',"engine":"', tbl_tuple.5, '"',
|
||||
',"collation":"",',
|
||||
'"size":', toString(tbl_tuple.6), ',',
|
||||
'"comment":', if(tbl_tuple.7 = '', '""', toString(toJSONString(tbl_tuple.7))), '}'),
|
||||
groupArray((
|
||||
tbl.database, -- tbl_tuple.1
|
||||
tbl.name, -- tbl_tuple.2
|
||||
tbl.total_rows, -- tbl_tuple.3
|
||||
tbl.type, -- tbl_tuple.4
|
||||
tbl.engine, -- tbl_tuple.5
|
||||
coalesce(ts.size, 0), -- tbl_tuple.6
|
||||
tbl.comment -- tbl_tuple.7
|
||||
))
|
||||
), ',') AS tbls_metadata
|
||||
FROM (
|
||||
SELECT
|
||||
tbl.database,
|
||||
tbl.name,
|
||||
coalesce(tbl.total_rows, 0) as total_rows,
|
||||
-- Determine the type based on the engine
|
||||
if(tbl.engine = 'View', 'VIEW',
|
||||
if(tbl.engine = 'MaterializedView', 'MATERIALIZED VIEW', 'TABLE')) AS type,
|
||||
tbl.engine,
|
||||
tbl.comment
|
||||
FROM system.tables AS tbl
|
||||
WHERE lower(tbl.database) NOT IN ('system', 'information_schema')
|
||||
AND lower(tbl.name) NOT LIKE '.inner_id.%'
|
||||
AND tbl.is_temporary = 0
|
||||
) AS tbl
|
||||
LEFT JOIN tbl_sizes AS ts
|
||||
ON tbl.database = ts.database AND tbl.name = ts.table
|
||||
-- GROUP BY tbl.database, tbl.name, tbl.total_rows, tbl.type, tbl.engine, tbl.comment, ts.size
|
||||
),
|
||||
indexes AS (
|
||||
SELECT arrayStringConcat(arrayMap((db, tbl, name) ->
|
||||
concat('{"schema":"', db, '"',
|
||||
',"table":"', tbl, '"',
|
||||
',"name":"', name, '"',
|
||||
',"index_type":"",',
|
||||
'"cardinality":"",',
|
||||
'"size":"",',
|
||||
'"unique":"false"}'),
|
||||
groupArray((idx.database, idx.table, idx.name))
|
||||
), ',') AS indexes_metadata
|
||||
FROM system.data_skipping_indices AS idx
|
||||
WHERE lower(idx.database) NOT IN ('system', 'information_schema')
|
||||
AND lower(idx.table) NOT LIKE '.inner_id.%'
|
||||
),
|
||||
views AS (
|
||||
SELECT arrayStringConcat(arrayMap((db, name, definition) ->
|
||||
concat('{"schema":"', db, '"',
|
||||
',"view_name":"', name, '"',
|
||||
',"view_definition":"',
|
||||
base64Encode(replaceAll(replaceAll(definition, '\\\\', '\\\\\\\\'), '"', '\\\\"')), '"}'),
|
||||
groupArray((vw.database, vw.name, vw.create_table_query))
|
||||
), ',') AS views_metadata
|
||||
FROM system.tables AS vw
|
||||
WHERE vw.engine in ('View', 'MaterializedView')
|
||||
AND lower(vw.database) NOT IN ('system', 'information_schema')
|
||||
),
|
||||
pks AS (
|
||||
SELECT
|
||||
col.database AS schema_name,
|
||||
col.table AS table_name,
|
||||
groupArray(col.name) AS pk_columns,
|
||||
concat('PRIMARY KEY(', arrayStringConcat(groupArray(col.name), ', '), ')') AS pk_def
|
||||
FROM system.columns AS col
|
||||
WHERE col.is_in_primary_key = 1
|
||||
AND lower(col.database) NOT IN ('system', 'information_schema')
|
||||
AND lower(col.table) NOT LIKE '.inner_id.%'
|
||||
GROUP BY col.database, col.table
|
||||
),
|
||||
pks_metadata AS (
|
||||
SELECT arrayStringConcat(arrayMap(pk_tuple ->
|
||||
concat('{"schema":"', pk_tuple.1, '"',
|
||||
',"table":"', pk_tuple.2, '"',
|
||||
',"column":"', pk_tuple.3, '"',
|
||||
',"pk_def":"', pk_tuple.4, '"}'),
|
||||
groupArray((
|
||||
pks.schema_name,
|
||||
pks.table_name,
|
||||
arrayJoin(pks.pk_columns),
|
||||
pks.pk_def
|
||||
))
|
||||
), ',') AS pk_metadata
|
||||
FROM pks
|
||||
)
|
||||
SELECT
|
||||
concat('{
|
||||
"fk_info": [],',
|
||||
'"pk_info": [', COALESCE((SELECT pk_metadata FROM pks_metadata), ''), '],',
|
||||
'"columns": [', COALESCE((SELECT cols_metadata FROM cols), ''),
|
||||
'], "indexes": [', COALESCE((SELECT indexes_metadata FROM indexes), ''),
|
||||
'], "tables":[', COALESCE((SELECT tbls_metadata FROM tbls), ''),
|
||||
'], "views":[', COALESCE((SELECT views_metadata FROM views), ''),
|
||||
'], "database_name": "', currentDatabase(), '", "version": "', version(), '"}'
|
||||
) AS metadata_json_to_import;`;
|
||||
196
src/lib/data/import-metadata/scripts/cockroachdb-script.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
const cockroachdbFilters = `
|
||||
AND connamespace::regnamespace::text NOT IN ('pg_extension', 'crdb_internal')
|
||||
`;
|
||||
|
||||
const cockroachdbColFilter = `
|
||||
AND cols.table_schema NOT IN ('pg_extension', 'crdb_internal')
|
||||
`;
|
||||
|
||||
const cockroachdbTableFilter = `
|
||||
AND tbls.table_schema NOT IN ('pg_extension', 'crdb_internal')
|
||||
`;
|
||||
|
||||
const cockroachdbIndexesFilter = `
|
||||
WHERE schema_name NOT IN ('pg_extension', 'crdb_internal')
|
||||
`;
|
||||
|
||||
const cockroachdbViewsFilter = `
|
||||
AND views.schemaname NOT IN ('pg_extension', 'crdb_internal')
|
||||
`;
|
||||
|
||||
export const cockroachdbQuery = `${`/* CockroachDB - PostgreSQL edition */`}
|
||||
WITH fk_info AS (
|
||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', replace(schema_name::TEXT, '"', ''), '"',
|
||||
',"table":"', replace(table_name::TEXT, '"', ''), '"',
|
||||
',"column":"', replace(fk_column::TEXT, '"', ''), '"',
|
||||
',"foreign_key_name":"', foreign_key_name::TEXT, '"',
|
||||
',"reference_schema":"', COALESCE(reference_schema::TEXT, 'public'), '"',
|
||||
',"reference_table":"', reference_table::TEXT, '"',
|
||||
',"reference_column":"', reference_column::TEXT, '"',
|
||||
',"fk_def":"', replace(fk_def::TEXT, '"', ''),
|
||||
'"}')), ',') as fk_metadata
|
||||
FROM (
|
||||
SELECT c.conname AS foreign_key_name,
|
||||
n.nspname AS schema_name,
|
||||
CASE
|
||||
WHEN position('.' in conrelid::regclass::text) > 0
|
||||
THEN split_part(conrelid::regclass::text, '.', 2)
|
||||
ELSE conrelid::regclass::text
|
||||
END AS table_name,
|
||||
a.attname AS fk_column,
|
||||
nr.nspname AS reference_schema,
|
||||
CASE
|
||||
WHEN position('.' in confrelid::regclass::text) > 0
|
||||
THEN split_part(confrelid::regclass::text, '.', 2)
|
||||
ELSE confrelid::regclass::text
|
||||
END AS reference_table,
|
||||
af.attname AS reference_column,
|
||||
pg_get_constraintdef(c.oid) as fk_def
|
||||
FROM
|
||||
pg_constraint AS c
|
||||
JOIN
|
||||
pg_attribute AS a ON a.attnum = ANY(c.conkey) AND a.attrelid = c.conrelid
|
||||
JOIN
|
||||
pg_class AS cl ON cl.oid = c.conrelid
|
||||
JOIN
|
||||
pg_namespace AS n ON n.oid = cl.relnamespace
|
||||
JOIN
|
||||
pg_attribute AS af ON af.attnum = ANY(c.confkey) AND af.attrelid = c.confrelid
|
||||
JOIN
|
||||
pg_class AS clf ON clf.oid = c.confrelid
|
||||
JOIN
|
||||
pg_namespace AS nr ON nr.oid = clf.relnamespace
|
||||
WHERE
|
||||
c.contype = 'f'
|
||||
AND connamespace::regnamespace::text NOT IN ('information_schema', 'pg_catalog')${cockroachdbFilters}
|
||||
) AS x
|
||||
), pk_info AS (
|
||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', replace(schema_name::TEXT, '"', ''), '"',
|
||||
',"table":"', replace(pk_table::TEXT, '"', ''), '"',
|
||||
',"column":"', replace(pk_column::TEXT, '"', ''), '"',
|
||||
',"pk_def":"', replace(pk_def::TEXT, '"', ''),
|
||||
'"}')), ',') AS pk_metadata
|
||||
FROM (
|
||||
SELECT connamespace::regnamespace::text AS schema_name,
|
||||
CASE
|
||||
WHEN strpos(conrelid::regclass::text, '.') > 0
|
||||
THEN split_part(conrelid::regclass::text, '.', 2)
|
||||
ELSE conrelid::regclass::text
|
||||
END AS pk_table,
|
||||
unnest(string_to_array(substring(pg_get_constraintdef(oid) FROM '\\((.*?)\\)'), ',')) AS pk_column,
|
||||
pg_get_constraintdef(oid) as pk_def
|
||||
FROM
|
||||
pg_constraint
|
||||
WHERE
|
||||
contype = 'p'
|
||||
AND connamespace::regnamespace::text NOT IN ('information_schema', 'pg_catalog')${cockroachdbFilters}
|
||||
) AS y
|
||||
),
|
||||
indexes_cols AS (
|
||||
SELECT tnsp.nspname AS schema_name,
|
||||
trel.relname AS table_name,
|
||||
null AS index_size,
|
||||
irel.relname AS index_name,
|
||||
am.amname AS index_type,
|
||||
a.attname AS col_name,
|
||||
(CASE WHEN i.indisunique = TRUE THEN 'true' ELSE 'false' END) AS is_unique,
|
||||
irel.reltuples AS cardinality,
|
||||
1 + Array_position(i.indkey, a.attnum) AS column_position,
|
||||
CASE o.OPTION & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
|
||||
CASE WHEN indpred IS NOT NULL THEN 'true' ELSE 'false' END AS is_partial_index
|
||||
FROM pg_index AS i
|
||||
JOIN pg_class AS trel ON trel.oid = i.indrelid
|
||||
JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
|
||||
JOIN pg_class AS irel ON irel.oid = i.indexrelid
|
||||
JOIN pg_am AS am ON irel.relam = am.oid
|
||||
CROSS JOIN LATERAL unnest (i.indkey)
|
||||
WITH ORDINALITY AS c (colnum, ordinality) LEFT JOIN LATERAL unnest (i.indoption)
|
||||
WITH ORDINALITY AS o (option, ordinality)
|
||||
ON c.ordinality = o.ordinality JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
|
||||
WHERE tnsp.nspname NOT LIKE 'pg_%'
|
||||
GROUP BY tnsp.nspname, trel.relname, irel.relname, am.amname, i.indisunique, i.indexrelid, irel.reltuples, a.attname, Array_position(i.indkey, a.attnum), o.OPTION, i.indpred
|
||||
),
|
||||
cols AS (
|
||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', cols.table_schema::TEXT,
|
||||
'","table":"', cols.table_name::TEXT,
|
||||
'","name":"', cols.column_name::TEXT,
|
||||
'","ordinal_position":"', cols.ordinal_position::TEXT,
|
||||
'","type":"', LOWER(replace(cols.data_type::TEXT, '"', '')),
|
||||
'","character_maximum_length":"', COALESCE(cols.character_maximum_length::TEXT, 'null'),
|
||||
'","precision":',
|
||||
CASE
|
||||
WHEN cols.data_type = 'numeric' OR cols.data_type = 'decimal'
|
||||
THEN CONCAT('{"precision":', COALESCE(cols.numeric_precision::TEXT, 'null'),
|
||||
',"scale":', COALESCE(cols.numeric_scale::TEXT, 'null'), '}')
|
||||
ELSE 'null'
|
||||
END,
|
||||
',"nullable":', CASE WHEN (cols.IS_NULLABLE = 'YES') THEN 'true' ELSE 'false' END::TEXT,
|
||||
',"default":"', COALESCE(replace(replace(cols.column_default::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
|
||||
'","collation":"', COALESCE(cols.COLLATION_NAME::TEXT, ''),
|
||||
'","comment":"', COALESCE(replace(replace(dsc.description::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
|
||||
'"}')), ',') AS cols_metadata
|
||||
FROM information_schema.columns cols
|
||||
LEFT JOIN pg_catalog.pg_class c
|
||||
ON c.relname = cols.table_name
|
||||
JOIN pg_catalog.pg_namespace n
|
||||
ON n.oid = c.relnamespace AND n.nspname = cols.table_schema
|
||||
LEFT JOIN pg_catalog.pg_description dsc ON dsc.objoid = c.oid
|
||||
AND dsc.objsubid = cols.ordinal_position
|
||||
WHERE cols.table_schema NOT IN ('information_schema', 'pg_catalog')${cockroachdbColFilter}
|
||||
), indexes_metadata AS (
|
||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', schema_name::TEXT,
|
||||
'","table":"', table_name::TEXT,
|
||||
'","name":"', index_name::TEXT,
|
||||
'","column":"', replace(col_name::TEXT, '"', E'"'),
|
||||
'","index_type":"', index_type::TEXT,
|
||||
'","cardinality":', COALESCE(cardinality::TEXT, '0'),
|
||||
',"size":', COALESCE(index_size::TEXT, 'null'),
|
||||
',"unique":', is_unique::TEXT,
|
||||
',"is_partial_index":', is_partial_index::TEXT,
|
||||
',"column_position":', column_position::TEXT,
|
||||
',"direction":"', LOWER(direction::TEXT),
|
||||
'"}')), ',') AS indexes_metadata
|
||||
FROM indexes_cols x${cockroachdbIndexesFilter}
|
||||
), tbls AS (
|
||||
SELECT array_to_string(array_agg(CONCAT('{',
|
||||
'"schema":"', tbls.TABLE_SCHEMA::TEXT, '",',
|
||||
'"table":"', tbls.TABLE_NAME::TEXT, '",',
|
||||
'"rows":', COALESCE((SELECT s.n_live_tup::TEXT
|
||||
FROM pg_stat_user_tables s
|
||||
WHERE tbls.TABLE_SCHEMA = s.schemaname AND tbls.TABLE_NAME = s.relname),
|
||||
'0'), ', "type":"', tbls.TABLE_TYPE::TEXT, '",', '"engine":"",', '"collation":"",',
|
||||
'"comment":"', COALESCE(replace(replace(dsc.description::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
|
||||
'"}'
|
||||
)),
|
||||
',') AS tbls_metadata
|
||||
FROM information_schema.tables tbls
|
||||
LEFT JOIN pg_catalog.pg_class c ON c.relname = tbls.TABLE_NAME
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
AND n.nspname = tbls.TABLE_SCHEMA
|
||||
LEFT JOIN pg_catalog.pg_description dsc ON dsc.objoid = c.oid
|
||||
AND dsc.objsubid = 0
|
||||
WHERE tbls.TABLE_SCHEMA NOT IN ('information_schema', 'pg_catalog')${cockroachdbTableFilter}
|
||||
), config AS (
|
||||
SELECT array_to_string(
|
||||
array_agg(CONCAT('{"name":"', conf.name, '","value":"', replace(conf.setting, '"', E'"'), '"}')),
|
||||
',') AS config_metadata
|
||||
FROM pg_settings conf
|
||||
), views AS (
|
||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', views.schemaname::TEXT,
|
||||
'","view_name":"', viewname::TEXT,
|
||||
'","view_definition":"', encode(convert_to(REPLACE(definition::TEXT, '"', '\\"'), 'UTF8'), 'base64'),
|
||||
'"}')),
|
||||
',') AS views_metadata
|
||||
FROM pg_views views
|
||||
WHERE views.schemaname NOT IN ('information_schema', 'pg_catalog')${cockroachdbViewsFilter}
|
||||
)
|
||||
SELECT CONCAT('{ "fk_info": [', COALESCE(fk_metadata, ''),
|
||||
'], "pk_info": [', COALESCE(pk_metadata, ''),
|
||||
'], "columns": [', COALESCE(cols_metadata, ''),
|
||||
'], "indexes": [', COALESCE(indexes_metadata, ''),
|
||||
'], "tables":[', COALESCE(tbls_metadata, ''),
|
||||
'], "views":[', COALESCE(views_metadata, ''),
|
||||
'], "database_name": "', CURRENT_DATABASE(), '', '", "version": "', '',
|
||||
'"}') AS metadata_json_to_import
|
||||
FROM fk_info, pk_info, cols, indexes_metadata, tbls, config, views;
|
||||
`;
|
||||
@@ -6,6 +6,8 @@ import { getSqlServerQuery } from './sqlserver-script';
|
||||
import { mariaDBQuery } from './maria-script';
|
||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||
import type { DatabaseClient } from '@/lib/domain/database-clients';
|
||||
import { clickhouseQuery } from './clickhouse-script';
|
||||
import { cockroachdbQuery } from './cockroachdb-script';
|
||||
|
||||
export type ImportMetadataScripts = Record<
|
||||
DatabaseType,
|
||||
@@ -22,4 +24,6 @@ export const importMetadataScripts: ImportMetadataScripts = {
|
||||
[DatabaseType.SQLITE]: () => sqliteQuery,
|
||||
[DatabaseType.SQL_SERVER]: getSqlServerQuery,
|
||||
[DatabaseType.MARIADB]: () => mariaDBQuery,
|
||||
[DatabaseType.CLICKHOUSE]: () => clickhouseQuery,
|
||||
[DatabaseType.COCKROACHDB]: () => cockroachdbQuery,
|
||||
};
|
||||
|
||||
@@ -76,9 +76,9 @@ cols AS (
|
||||
ELSE
|
||||
'null'
|
||||
END +
|
||||
', "nullable": "' +
|
||||
', "nullable": ' +
|
||||
CASE WHEN cols.IS_NULLABLE = 'YES' THEN 'true' ELSE 'false' END +
|
||||
'", "default": "' +
|
||||
', "default": "' +
|
||||
COALESCE(REPLACE(CAST(cols.COLUMN_DEFAULT AS NVARCHAR(MAX)), '"', '\\"'), '') +
|
||||
'", "collation": "' +
|
||||
COALESCE(cols.COLLATION_NAME, '') +
|
||||
@@ -308,9 +308,9 @@ cols AS (
|
||||
ELSE
|
||||
'null'
|
||||
END +
|
||||
', "nullable": "' +
|
||||
', "nullable": ' +
|
||||
CASE WHEN cols.IS_NULLABLE = 'YES' THEN 'true' ELSE 'false' END +
|
||||
'", "default": "' +
|
||||
', "default": "' +
|
||||
COALESCE(REPLACE(CAST(cols.COLUMN_DEFAULT AS NVARCHAR(MAX)), '"', '"'), '') +
|
||||
'", "collation": "' +
|
||||
COALESCE(cols.COLLATION_NAME, '') +
|
||||
|
||||
@@ -14,6 +14,12 @@ import MariaDBLogo2 from '@/assets/mariadb_logo_2.png';
|
||||
import SqliteLogo2 from '@/assets/sqlite_logo_2.png';
|
||||
import SqlServerLogo2 from '@/assets/sql_server_logo_2.png';
|
||||
import GeneralDBLogo2 from '@/assets/general_db_logo_2.png';
|
||||
import ClickhouseLogo from '@/assets/clickhouse_logo.png';
|
||||
import ClickhouseLogoDark from '@/assets/clickhouse_logo_dark.png';
|
||||
import ClickhouseLogo2 from '@/assets/clickhouse_logo_2.png';
|
||||
import CockroachDBLogo from '@/assets/cockroachdb_logo.png';
|
||||
import CockroachDBLogoDark from '@/assets/cockroachdb_logo_dark.png';
|
||||
import CockroachDBLogo2 from '@/assets/cockroachdb_logo_2.png';
|
||||
import { DatabaseType } from './domain/database-type';
|
||||
import type { EffectiveTheme } from '@/context/theme-context/theme-context';
|
||||
|
||||
@@ -24,6 +30,8 @@ export const databaseTypeToLabelMap: Record<DatabaseType, string> = {
|
||||
[DatabaseType.SQL_SERVER]: 'SQL Server',
|
||||
[DatabaseType.MARIADB]: 'MariaDB',
|
||||
[DatabaseType.SQLITE]: 'SQLite',
|
||||
[DatabaseType.CLICKHOUSE]: 'ClickHouse',
|
||||
[DatabaseType.COCKROACHDB]: 'CockroachDB',
|
||||
};
|
||||
|
||||
export const databaseLogoMap: Record<DatabaseType, string> = {
|
||||
@@ -32,6 +40,8 @@ export const databaseLogoMap: Record<DatabaseType, string> = {
|
||||
[DatabaseType.MARIADB]: MariaDBLogo,
|
||||
[DatabaseType.SQLITE]: SqliteLogo,
|
||||
[DatabaseType.SQL_SERVER]: SqlServerLogo,
|
||||
[DatabaseType.CLICKHOUSE]: ClickhouseLogo,
|
||||
[DatabaseType.COCKROACHDB]: CockroachDBLogo,
|
||||
[DatabaseType.GENERIC]: '',
|
||||
};
|
||||
|
||||
@@ -41,6 +51,8 @@ export const databaseDarkLogoMap: Record<DatabaseType, string> = {
|
||||
[DatabaseType.MARIADB]: MariaDBLogoDark,
|
||||
[DatabaseType.SQLITE]: SqliteLogoDark,
|
||||
[DatabaseType.SQL_SERVER]: SqlServerLogoDark,
|
||||
[DatabaseType.CLICKHOUSE]: ClickhouseLogoDark,
|
||||
[DatabaseType.COCKROACHDB]: CockroachDBLogoDark,
|
||||
[DatabaseType.GENERIC]: '',
|
||||
};
|
||||
|
||||
@@ -58,5 +70,7 @@ export const databaseSecondaryLogoMap: Record<DatabaseType, string> = {
|
||||
[DatabaseType.MARIADB]: MariaDBLogo2,
|
||||
[DatabaseType.SQLITE]: SqliteLogo2,
|
||||
[DatabaseType.SQL_SERVER]: SqlServerLogo2,
|
||||
[DatabaseType.CLICKHOUSE]: ClickhouseLogo2,
|
||||
[DatabaseType.COCKROACHDB]: CockroachDBLogo2,
|
||||
[DatabaseType.GENERIC]: GeneralDBLogo2,
|
||||
};
|
||||
|
||||
@@ -18,4 +18,6 @@ export const databaseTypeToClientsMap: Record<DatabaseType, DatabaseClient[]> =
|
||||
[DatabaseType.GENERIC]: [],
|
||||
[DatabaseType.SQL_SERVER]: [],
|
||||
[DatabaseType.MARIADB]: [],
|
||||
[DatabaseType.CLICKHOUSE]: [],
|
||||
[DatabaseType.COCKROACHDB]: [],
|
||||
};
|
||||
|
||||
@@ -51,4 +51,6 @@ export const databaseTypeToEditionMap: Record<DatabaseType, DatabaseEdition[]> =
|
||||
[DatabaseType.SQLITE]: [],
|
||||
[DatabaseType.GENERIC]: [],
|
||||
[DatabaseType.MARIADB]: [],
|
||||
[DatabaseType.CLICKHOUSE]: [],
|
||||
[DatabaseType.COCKROACHDB]: [],
|
||||
};
|
||||
|
||||
@@ -5,4 +5,6 @@ export enum DatabaseType {
|
||||
SQL_SERVER = 'sql_server',
|
||||
MARIADB = 'mariadb',
|
||||
SQLITE = 'sqlite',
|
||||
CLICKHOUSE = 'clickhouse',
|
||||
COCKROACHDB = 'cockroachdb',
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ const astDatabaseTypes: Record<DatabaseType, string> = {
|
||||
[DatabaseType.GENERIC]: 'postgresql',
|
||||
[DatabaseType.SQLITE]: 'postgresql',
|
||||
[DatabaseType.SQL_SERVER]: 'postgresql',
|
||||
[DatabaseType.CLICKHOUSE]: 'postgresql',
|
||||
[DatabaseType.COCKROACHDB]: 'postgresql',
|
||||
};
|
||||
|
||||
export const createDependenciesFromMetadata = async ({
|
||||
|
||||
@@ -21,4 +21,6 @@ export const schemaNameToDomainSchemaName = (
|
||||
export const databasesWithSchemas: DatabaseType[] = [
|
||||
DatabaseType.POSTGRESQL,
|
||||
DatabaseType.SQL_SERVER,
|
||||
DatabaseType.CLICKHOUSE,
|
||||
DatabaseType.COCKROACHDB,
|
||||
];
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface DBTable {
|
||||
createdAt: number;
|
||||
width?: number;
|
||||
comments?: string;
|
||||
hidden?: boolean;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export const dbTableSchema: z.ZodType<DBTable> = z.object({
|
||||
@@ -58,6 +58,7 @@ export const dbTableSchema: z.ZodType<DBTable> = z.object({
|
||||
width: z.number().optional(),
|
||||
comments: z.string().optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
order: z.number().optional(),
|
||||
});
|
||||
|
||||
export const shouldShowTablesBySchemaFilter = (
|
||||
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
TARGET_DEP_PREFIX,
|
||||
TOP_SOURCE_HANDLE_ID_PREFIX,
|
||||
} from './table-node/table-node-dependency-indicator';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
|
||||
export type EdgeType = RelationshipEdgeType | DependencyEdgeType;
|
||||
|
||||
@@ -216,11 +217,19 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables, readonly }) => {
|
||||
targetHandle: `${TARGET_DEP_PREFIX}${targetDepIndexes[dep.tableId]++}_${dep.tableId}`,
|
||||
type: 'dependency-edge',
|
||||
data: { dependency: dep },
|
||||
hidden: !showDependenciesOnCanvas,
|
||||
hidden:
|
||||
!showDependenciesOnCanvas &&
|
||||
databaseType !== DatabaseType.CLICKHOUSE,
|
||||
})
|
||||
),
|
||||
]);
|
||||
}, [relationships, dependencies, setEdges, showDependenciesOnCanvas]);
|
||||
}, [
|
||||
relationships,
|
||||
dependencies,
|
||||
setEdges,
|
||||
showDependenciesOnCanvas,
|
||||
databaseType,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedNodesIds = nodes
|
||||
|
||||
@@ -93,7 +93,14 @@ const EditorPageComponent: React.FC = () => {
|
||||
resetUndoStack();
|
||||
const diagram = await loadDiagram(diagramId);
|
||||
if (!diagram) {
|
||||
navigate('/');
|
||||
if (currentDiagram?.id) {
|
||||
await updateConfig({
|
||||
defaultDiagramId: currentDiagram.id,
|
||||
});
|
||||
navigate(`/diagrams/${currentDiagram.id}`);
|
||||
} else {
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
setInitialDiagram(diagram);
|
||||
hideLoader();
|
||||
|
||||
@@ -48,7 +48,7 @@ export const TableField: React.FC<TableFieldProps> = ({
|
||||
}));
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transform: CSS.Translate.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
CircleDotDashed,
|
||||
GripVertical,
|
||||
Pencil,
|
||||
EllipsisVertical,
|
||||
Trash2,
|
||||
@@ -15,6 +16,7 @@ import type { DBTable } from '@/lib/domain/db-table';
|
||||
import { Input } from '@/components/input/input';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useClickAway, useKeyPressEvent } from 'react-use';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -60,6 +62,7 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
||||
const [tableName, setTableName] = React.useState(table.name);
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const { listeners } = useSortable({ id: table.id });
|
||||
|
||||
const editTableName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
@@ -252,6 +255,12 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
||||
|
||||
return (
|
||||
<div className="group flex h-11 flex-1 items-center justify-between gap-1 overflow-hidden">
|
||||
<div
|
||||
className="flex cursor-move items-center justify-center"
|
||||
{...listeners}
|
||||
>
|
||||
<GripVertical className="size-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 px-1">
|
||||
{editMode ? (
|
||||
<Input
|
||||
|
||||