Compare commits

..

77 Commits

Author SHA1 Message Date
Guy Ben-Aharon
47bb87a88f chore(main): release 1.8.0 (#558) 2025-02-13 15:49:53 +02:00
Guy Ben-Aharon
a96c2e1078 fix(docker): add option to hide popups (#580) 2025-02-13 15:47:02 +02:00
Guy Ben-Aharon
26d95eed25 fix(table actions): fix size of table actions (#578) 2025-02-11 16:22:54 +02:00
Jonathan Fishner
be65328f24 fix(mssql-import): improve script readability by adding edition comment (#572)
* fix(mssql-import): improve script readability by adding edition comment

* fix(mssql-import): fixing issues with import query
2025-02-10 11:00:51 +02:00
Jonathan Fishner
85fd14fa02 fix(export-sql): show create script for only filtered schemas (#570)
* fix(export-sql): show create script for only filtered schemas

* add dependencies filter

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-10 10:54:22 +02:00
Jonathan Fishner
9c485b3b01 fix(sqlserver-import): open ssms guide when max chars (#565)
* fix(sqlserver-import): open ssms guide when max chars (truncated chars) for input

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-10 10:37:32 +02:00
Jonathan Fishner
e993f1549c fix(canvas): add right-click option to create relationships (#568)
* feat(create-relationship): add right-click option to easy create relationships

* add missing translations

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-10 10:06:31 +02:00
Jonathan Fishner
0db67ea42a fix(import dbml): add import for indexes (#566) 2025-02-10 09:12:11 +02:00
Jonathan Fishner
b9e621bd68 fix(realtionships section): add the schema to source/target tables (#561) 2025-02-09 23:23:11 +02:00
Jonathan Fishner
93d59f8887 fix(import-query): improve the cleanup for messy json input (#562) 2025-02-07 14:03:38 +02:00
Jonathan Fishner
190e4f4ffa feat(dbml-import): add error highlighting for dbml imports (#556)
* feat(dbml-import): add error highlighting for dbml imports

* fix deprecated api

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-06 21:42:14 +02:00
Guy Ben-Aharon
dc404c9d7e fix(canvas): locate table from canvas (#560) 2025-02-06 21:39:55 +02:00
Guy Ben-Aharon
dd4324d64f fix(index unique): extract unique toggle for faster editing (#559) 2025-02-06 21:37:11 +02:00
Calum Siemer
1878083056 feat(docker image): add support for custom inference servers (#543)
- Add OPENAI_API_ENDPOINT configuration
- Add LLM_MODEL_NAME configuration
- Update documentation for custom server setup
- Add error handling for endpoint configuration
2025-02-06 20:16:15 +02:00
Andrii Holovin
7b6271962a fix(i18n): fix Ukrainian (#554) 2025-02-06 18:59:06 +02:00
Guy Ben-Aharon
2edc8dfde8 chore(main): release 1.7.0 (#539) 2025-02-05 12:49:38 +02:00
Guy Ben-Aharon
004d530880 fix(performance): reduce bundle size (#553) 2025-02-03 12:08:02 +02:00
Guy Ben-Aharon
fd2cc9fcfc fix(performance): resolve error on startup (#552) 2025-02-02 17:19:53 +02:00
Guy Ben-Aharon
4c93326bb6 fix(performance): fix bundle size (#551) 2025-02-02 16:45:16 +02:00
Guy Ben-Aharon
ef3d7a8b67 fix(scroll): fix scroll area (#550) 2025-02-02 16:34:18 +02:00
Jonathan Fishner
3b3be086b1 fix(dbml-editor): add shortcuts to dbml and filter: #534 (#535)
* fix(dbml-editor): add shortcuts to dbml and filter

* some fixes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-02 15:36:53 +02:00
Jonathan Fishner
b424518212 feat(import-dbml): add import dbml functionality (#549)
* feat(import-dbml): add import dbml functionality

* fix: fit to view do not run after every tbl move

* fix: add missing translation keys

* some fixes

* some fixes

* fix: update for longer timeout before fitToView

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-02 15:20:01 +02:00
Jonathan Fishner
99a8201398 fix(empty-state): fix dark-mode for empty-state (#547) 2025-02-02 11:48:09 +02:00
Jonathan Fishner
eb9b41e4f6 fix(psql-import): remove typo for import command (psql) (#546) 2025-01-30 14:53:01 +02:00
Guy Ben-Aharon
fef6d3f499 fix(dbml): add error handling (#545) 2025-01-28 13:18:54 +02:00
Jonathan Fishner
14f11c27a7 fix(open-diagram): add arrow keys navigation in open diagram dialog (#537)
* fix(open-diagram): add arrow keys navigation in open diagram dialog

* some refining

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-28 13:16:14 +02:00
Guy Ben-Aharon
2118bce0f0 fix(examples): fix employee example dbml (#544) 2025-01-28 09:49:21 +02:00
Jonathan Fishner
88be6c1fd4 feat(dbml-editor): add dbml editor in side pannel (#534)
* add dbml mode

* add dark theme to dbml editor

* add the same sort logic to the dbml

* add comments to dbml

* fix colors + filtering

* fix(dbml-editor): add read-only toast for dbml editor

* update toast location

* fix(translations): add dbml translations

* dbml ui improvements

* dbml ui improvements

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-27 20:32:39 +02:00
Jonathan Fishner
0dcc9b9568 fix(canvas edit): add option to edit names in canvas (#536)
* fix(canvas edit): add option to edit names in canvas

* fix(canvas edit): add option to edit field names in canvas

* some few fixes + style

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-27 14:26:04 +02:00
Andrii Holovin
ff3269ec05 fix(i18n): translation/Ukrainian (#529)
* Update uk.ts

* Apply suggestions from code review

Fix linter issues

---------

Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2025-01-27 12:47:52 +02:00
Guy Ben-Aharon
659dc2e3e7 chore(main): release 1.6.1 (#527)
* chore(main): release 1.6.1

* fix
2025-01-26 19:35:50 +02:00
Jonathan Fishner
c36cd33180 fix(filter-tables) show clean filter if no-results (#532)
* fix(filter-tables) show clean filter if no-results

* add translations

* remove some css classes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-26 19:27:35 +02:00
Jonathan Fishner
58231c9139 fix(chat-type): remove typo of char datatype in examples (#530) 2025-01-26 17:43:21 +02:00
Guy Ben-Aharon
1643e7bdeb fix(empty_state): customize empty state (#533) 2025-01-26 14:19:02 +02:00
Guy Ben-Aharon
42d4cbac8c fix: change empty state image (#531) 2025-01-26 13:25:30 +02:00
Jonathan Fishner
7452ca6965 fix(shortcuts): add zoom all shortcut (#528) 2025-01-20 00:26:35 -03:00
Derick Ruiz
27aede7794 Updating the postgres-script so that the psql script creates an output.json with the result instead of using Mac-specific commands. (#520)
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2025-01-14 22:49:40 +02:00
Asgar Aliyev
e9e2736cb2 fix(Image Export): importing css rules error while download image (#524) 2025-01-14 17:39:38 -03:00
Guy Ben-Aharon
74c1730425 chore(main): release 1.6.0 (#512) 2025-01-03 15:41:56 +02:00
Guy Ben-Aharon
94bed7fcce refactor: move menu to a separate component (#517)
* refactor: move menu to a separate component

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

* fix build

* fix build

---------

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

* fix lint

---------

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

* refactor: improve formatting and structure of TableNodeField component

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

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

* refactor: standardize string formatting in Arabic locale files

* some alignments

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-12-26 20:00:56 +02:00
Guy Ben-Aharon
7868ca9f42 change last save to icon (#506) 2024-12-26 11:07:50 +02:00
Guy Ben-Aharon
0411742864 Fix multiple dialog pop-ups (#501) 2024-12-24 15:00:55 +02:00
Guy Ben-Aharon
9831ac5a10 add buckle dialog (#499) 2024-12-23 20:56:10 +02:00
Guy Ben-Aharon
91c6fb9249 add buckle dialog (#498) 2024-12-23 20:31:58 +02:00
Guy Ben-Aharon
c155013668 change label (#494) 2024-12-22 23:06:40 +02:00
Guy Ben-Aharon
1b0f293c87 remove diagrams from navbar + buckle (#490) 2024-12-22 19:52:16 +02:00
Guy Ben-Aharon
df2dc03aa0 open diagram dialog resizing (#493) 2024-12-22 16:13:29 +02:00
Guy Ben-Aharon
205d431c89 separate alert dialog from dialog context (#491) 2024-12-22 15:10:42 +02:00
Guy Ben-Aharon
0abe18cdf9 chore(main): release 1.5.1 (#483) 2024-12-22 10:42:44 +02:00
Guy Ben-Aharon
a151f56b5d fix(export): fix SQL server field.nullable type to boolean (#486)
* fix(export): fix SQL server field.nullable type to boolean

* fix build

* fix build
2024-12-15 17:18:57 +02:00
Jonathan Fishner
2b6b733261 fix(readme): Update README.md - add CockroachDB (#482) 2024-12-11 21:12:15 +02:00
Guy Ben-Aharon
b56b04925c chore(main): release 1.5.0 (#469)
* chore(main): release 1.5.0

* fix
2024-12-11 17:25:16 +02:00
Jonathan Fishner
635fb53c9f fix(sql-server import) nullable should be boolean instead of string (#480) 2024-12-11 16:39:40 +02:00
Guy Ben-Aharon
d6659795bc upgrade eslint (#479) 2024-12-11 12:43:45 +02:00
Guy Ben-Aharon
348f80568e fix(core): fix update diagram id (#477) 2024-12-11 00:14:19 +02:00
Guy Ben-Aharon
5f9c74a9ad make the diagram icon component flexible (#476) 2024-12-10 15:22:04 +02:00
Jonathan Fishner
5409288388 feat(CockroachDB): Add CockroachDB support (#472)
* feat(add CockroachDB): Add support in CockroachDB

* remove toggle from toolbar

* fix key

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-12-09 21:46:34 +02:00
Guy Ben-Aharon
2309306ef5 fix(dialogs): fix footer position on dialogs (#470) 2024-12-04 19:44:06 +02:00
Wisnu Wicaksono
3574cecc7c feat(i18n): translate share and dialog sections in Indonesian locale files (#468) 2024-12-04 11:28:40 +02:00
Guy Ben-Aharon
29b8edc051 chore(main): release 1.4.0 (#453) 2024-12-02 13:30:27 +02:00
Huy Bui
5fc10a7e64 fix(clipboard): defensive for navigator clipboard (#462)
* fix: defensive for navigator clipboard

* add dot in en.ts

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-12-02 13:27:13 +02:00
Guy Ben-Aharon
807cd22e0c feat(clickhouse): add ClickHouse support (#463) 2024-12-01 13:31:24 +02:00
Jonathan Fishner
03772f6b4f feat(add templates): add six more templates (django-axes, laravel-activitylog, octobox, pay-rails, pixelfed, polr) (#460) 2024-11-30 20:51:35 +02:00
Niharika Goulikar
885eb719de feat(i18n): Added bangla translations (#432) 2024-11-30 09:51:47 +02:00
Elliott Zwertvaegher
94656ec7a5 fix(mariadb-types): Add uuid data type (#459)
Madiadb is missing the UUID data type. I simply added it in `src/lib/data/data-types/mariadb-data-types.ts`
2024-11-29 12:21:43 +02:00
Zer0S2m
a0e966b64f feat(side-panel): Add functionality of order tables by drag & drop (#425) 2024-11-28 19:07:51 +02:00
Guy Ben-Aharon
a8fe491c1b fix(import-database): update database type after importing into an existing generic diagra (#456)
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2024-11-27 16:59:07 +02:00
Jonathan Fishner
ddeef3b134 feat(add templates): add six more templates (reversion, screeenly, staytus, deployer, devise, talk) (#457) 2024-11-27 16:18:51 +02:00
ntoniazzi
d45677e92d fix(Last Saved): Translate the "last saved" relative date message (#400)
* Translate the "last saved" relative date message

* dynamic import locale base on the selected lang

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-27 15:07:18 +02:00
Guy Ben-Aharon
9c7d03c285 fix: window type (#454) 2024-11-26 18:17:59 +02:00
Jonathan Fishner
be1b109f23 feat(add templates): add six more templates (#452)
* feat(add templates): add six more templates (taggit, orchid, flipper, doorkeeper, canvas, cachet)

* fix build
2024-11-26 18:07:41 +02:00
Guy Ben-Aharon
05eaf85a3d update readme (#450) 2024-11-26 14:22:50 +02:00
175 changed files with 44212 additions and 12035 deletions

View File

@@ -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' },
},
};

View File

@@ -1,5 +1,123 @@
# Changelog
## [1.8.0](https://github.com/chartdb/chartdb/compare/v1.7.0...v1.8.0) (2025-02-13)
### Features
* **dbml-import:** add error highlighting for dbml imports ([#556](https://github.com/chartdb/chartdb/issues/556)) ([190e4f4](https://github.com/chartdb/chartdb/commit/190e4f4ffa834fa621f264dc608ca3f3b393a331))
* **docker image:** add support for custom inference servers ([#543](https://github.com/chartdb/chartdb/issues/543)) ([1878083](https://github.com/chartdb/chartdb/commit/1878083056ea4db7a05cdeeb38a4f7b9f5f95bd1))
### Bug Fixes
* **canvas:** add right-click option to create relationships ([#568](https://github.com/chartdb/chartdb/issues/568)) ([e993f15](https://github.com/chartdb/chartdb/commit/e993f1549c4c86bb9e7e36062db803ba6613b3b3))
* **canvas:** locate table from canvas ([#560](https://github.com/chartdb/chartdb/issues/560)) ([dc404c9](https://github.com/chartdb/chartdb/commit/dc404c9d7ee272c93aac69646bac859829a5234e))
* **docker:** add option to hide popups ([#580](https://github.com/chartdb/chartdb/issues/580)) ([a96c2e1](https://github.com/chartdb/chartdb/commit/a96c2e107838d2dc13b586923fd9dbe06598cdd8))
* **export-sql:** show create script for only filtered schemas ([#570](https://github.com/chartdb/chartdb/issues/570)) ([85fd14f](https://github.com/chartdb/chartdb/commit/85fd14fa02bb2879c36bba53369dbf2e7fa578d4))
* **i18n:** fix Ukrainian ([#554](https://github.com/chartdb/chartdb/issues/554)) ([7b62719](https://github.com/chartdb/chartdb/commit/7b6271962a99bfe5ffbd0176e714c76368ef5c41))
* **import dbml:** add import for indexes ([#566](https://github.com/chartdb/chartdb/issues/566)) ([0db67ea](https://github.com/chartdb/chartdb/commit/0db67ea42a5f9585ca1d246db7a7ff0239bec0ba))
* **import-query:** improve the cleanup for messy json input ([#562](https://github.com/chartdb/chartdb/issues/562)) ([93d59f8](https://github.com/chartdb/chartdb/commit/93d59f8887765098d040a3184aaee32112f67267))
* **index unique:** extract unique toggle for faster editing ([#559](https://github.com/chartdb/chartdb/issues/559)) ([dd4324d](https://github.com/chartdb/chartdb/commit/dd4324d64f7638ada5c022a2ab38bd8e6986af25))
* **mssql-import:** improve script readability by adding edition comment ([#572](https://github.com/chartdb/chartdb/issues/572)) ([be65328](https://github.com/chartdb/chartdb/commit/be65328f24b0361638b9e2edb39eaa9906e77f67))
* **realtionships section:** add the schema to source/target tables ([#561](https://github.com/chartdb/chartdb/issues/561)) ([b9e621b](https://github.com/chartdb/chartdb/commit/b9e621bd680730a0ffbf1054d735bfa418711cae))
* **sqlserver-import:** open ssms guide when max chars ([#565](https://github.com/chartdb/chartdb/issues/565)) ([9c485b3](https://github.com/chartdb/chartdb/commit/9c485b3b01a131bf551c7e95916b0c416f6aa0b5))
* **table actions:** fix size of table actions ([#578](https://github.com/chartdb/chartdb/issues/578)) ([26d95ee](https://github.com/chartdb/chartdb/commit/26d95eed25d86452d9168a9d93a301ba50d934e3))
## [1.7.0](https://github.com/chartdb/chartdb/compare/v1.6.1...v1.7.0) (2025-02-03)
### Features
* **dbml-editor:** add dbml editor in side pannel ([#534](https://github.com/chartdb/chartdb/issues/534)) ([88be6c1](https://github.com/chartdb/chartdb/commit/88be6c1fd4a7e1f20937e8204c14d8fc1c2665b4))
* **import-dbml:** add import dbml functionality ([#549](https://github.com/chartdb/chartdb/issues/549)) ([b424518](https://github.com/chartdb/chartdb/commit/b424518212290a870fdb7c420a303f65f5901429))
### Bug Fixes
* **canvas edit:** add option to edit names in canvas ([#536](https://github.com/chartdb/chartdb/issues/536)) ([0dcc9b9](https://github.com/chartdb/chartdb/commit/0dcc9b9568cfe749d44d2e93cb365ba3d3a1e71c))
* **dbml-editor:** add shortcuts to dbml and filter: [#534](https://github.com/chartdb/chartdb/issues/534) ([#535](https://github.com/chartdb/chartdb/issues/535)) ([3b3be08](https://github.com/chartdb/chartdb/commit/3b3be086b1e8d5acf999f8504580d9e2f956f7da))
* **dbml:** add error handling ([#545](https://github.com/chartdb/chartdb/issues/545)) ([fef6d3f](https://github.com/chartdb/chartdb/commit/fef6d3f4996130a3769d1f25b4b1f2090293a1bf))
* **empty-state:** fix dark-mode for empty-state ([#547](https://github.com/chartdb/chartdb/issues/547)) ([99a8201](https://github.com/chartdb/chartdb/commit/99a820139861546a012d7b562ddbb9b77698151a))
* **examples:** fix employee example dbml ([#544](https://github.com/chartdb/chartdb/issues/544)) ([2118bce](https://github.com/chartdb/chartdb/commit/2118bce0f00d55eb19d22b9fa2d4964ba2533a09))
* **i18n:** translation/Ukrainian ([#529](https://github.com/chartdb/chartdb/issues/529)) ([ff3269e](https://github.com/chartdb/chartdb/commit/ff3269ec0510bbae4bc114e65a1ea86a656e8785))
* **open-diagram:** add arrow keys navigation in open diagram dialog ([#537](https://github.com/chartdb/chartdb/issues/537)) ([14f11c2](https://github.com/chartdb/chartdb/commit/14f11c27a7ad5b990131c8495148cabf12835082))
* **performance:** fix bundle size ([#551](https://github.com/chartdb/chartdb/issues/551)) ([4c93326](https://github.com/chartdb/chartdb/commit/4c93326bb6e3eaa143373c500a0c641e95a53fb9))
* **performance:** reduce bundle size ([#553](https://github.com/chartdb/chartdb/issues/553)) ([004d530](https://github.com/chartdb/chartdb/commit/004d530880a50dea6e9786eb9ae63cf592a4d852))
* **performance:** resolve error on startup ([#552](https://github.com/chartdb/chartdb/issues/552)) ([fd2cc9f](https://github.com/chartdb/chartdb/commit/fd2cc9fcfc8f4a9f0bc79def47d89114159392fb))
* **psql-import:** remove typo for import command (psql) ([#546](https://github.com/chartdb/chartdb/issues/546)) ([eb9b41e](https://github.com/chartdb/chartdb/commit/eb9b41e4f656bec1451c45763f4ea5b547aeec5c))
* **scroll:** fix scroll area ([#550](https://github.com/chartdb/chartdb/issues/550)) ([ef3d7a8](https://github.com/chartdb/chartdb/commit/ef3d7a8b67431e923b75bf8287b86bbc8abe723b))
## [1.6.1](https://github.com/chartdb/chartdb/compare/v1.6.0...v1.6.1) (2025-01-26)
### Bug Fixes
* change empty state image ([#531](https://github.com/chartdb/chartdb/issues/531)) ([42d4cba](https://github.com/chartdb/chartdb/commit/42d4cbac8ce352e0e4e155d7003bfb85296b897f))
* **chat-type:** remove typo of char datatype in examples ([#530](https://github.com/chartdb/chartdb/issues/530)) ([58231c9](https://github.com/chartdb/chartdb/commit/58231c91393de30ebff817f0ebc57a5c5579f106))
* **empty_state:** customize empty state ([#533](https://github.com/chartdb/chartdb/issues/533)) ([1643e7b](https://github.com/chartdb/chartdb/commit/1643e7bdeb1bbaf081ab064e871d102c87243c0a))
* **Image Export:** importing css rules error while download image ([#524](https://github.com/chartdb/chartdb/issues/524)) ([e9e2736](https://github.com/chartdb/chartdb/commit/e9e2736cb2203702d53df9afc30b8e989a8c9953))
* **shortcuts:** add zoom all shortcut ([#528](https://github.com/chartdb/chartdb/issues/528)) ([7452ca6](https://github.com/chartdb/chartdb/commit/7452ca6965b0332a93b686c397ddf51013e42506))
* **filter-tables:** show clean filter if no-results ([#532](https://github.com/chartdb/chartdb/issues/532)) ([c36cd33](https://github.com/chartdb/chartdb/commit/c36cd33180badaa9b7f9e27c765f19cb03a50ccd))
## [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)
### Bug Fixes
* **export:** fix SQL server field.nullable type to boolean ([#486](https://github.com/chartdb/chartdb/issues/486)) ([a151f56](https://github.com/chartdb/chartdb/commit/a151f56b5d950e0b5cc54363684ada95889024b3))
* **readme:** Update README.md - add CockroachDB ([#482](https://github.com/chartdb/chartdb/issues/482)) ([2b6b733](https://github.com/chartdb/chartdb/commit/2b6b73326155f18d6d56779c0657a3506e2d2cde))
## [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)

View File

@@ -1,6 +1,9 @@
FROM node:22-alpine AS builder
ARG VITE_OPENAI_API_KEY
ARG VITE_OPENAI_API_ENDPOINT
ARG VITE_LLM_MODEL_NAME
ARG VITE_HIDE_BUCKLE_DOT_DEV
WORKDIR /usr/src/app
@@ -10,9 +13,13 @@ RUN npm ci
COPY . .
RUN echo "VITE_OPENAI_API_KEY=${VITE_OPENAI_API_KEY}" > .env && \
echo "VITE_OPENAI_API_ENDPOINT=${VITE_OPENAI_API_ENDPOINT}" >> .env && \
echo "VITE_LLM_MODEL_NAME=${VITE_LLM_MODEL_NAME}" >> .env && \
echo "VITE_HIDE_BUCKLE_DOT_DEV=${VITE_HIDE_BUCKLE_DOT_DEV}" >> .env
RUN npm run build
# Use a lightweight web server to serve the production build
FROM nginx:stable-alpine AS production
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
@@ -20,7 +27,6 @@ COPY ./default.conf.template /etc/nginx/conf.d/default.conf.template
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Expose the default port for the Nginx web server
EXPOSE 80
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -68,6 +68,7 @@ ChartDB is currently in Public Beta. Star and watch this repository to get notif
- ✅ SQL Server
- ✅ MariaDB
- ✅ SQLite
- ✅ CockroachDB
- ✅ ClickHouse
## Getting Started
@@ -95,19 +96,44 @@ 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
```
#### Using Custom Inference Server
```bash
# Build
docker build \
--build-arg VITE_OPENAI_API_ENDPOINT=<YOUR_ENDPOINT> \
--build-arg VITE_LLM_MODEL_NAME=<YOUR_MODEL_NAME> \
-t chartdb .
# Run
docker run \
-e OPENAI_API_ENDPOINT=<YOUR_ENDPOINT> \
-e LLM_MODEL_NAME=<YOUR_MODEL_NAME> \
-p 8080:80 chartdb
```
> **Note:** You must configure either Option 1 (OpenAI API key) OR Option 2 (Custom endpoint and model name) for AI capabilities to work. Do not mix the two options.
Open your browser and navigate to `http://localhost:8080`.
Example configuration for a local vLLM server:
```bash
VITE_OPENAI_API_ENDPOINT=http://localhost:8000/v1
VITE_LLM_MODEL_NAME=Qwen/Qwen2.5-32B-Instruct-AWQ
```
## Try it on our website
1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)

View File

@@ -10,7 +10,12 @@ server {
location /config.js {
default_type application/javascript;
return 200 "window.env = { OPENAI_API_KEY: \"$OPENAI_API_KEY\" };";
return 200 "window.env = {
OPENAI_API_KEY: \"$OPENAI_API_KEY\",
OPENAI_API_ENDPOINT: \"$OPENAI_API_ENDPOINT\",
LLM_MODEL_NAME: \"$LLM_MODEL_NAME\",
HIDE_BUCKLE_DOT_DEV: \"$HIDE_BUCKLE_DOT_DEV\"
};";
}
error_page 500 502 503 504 /50x.html;

View File

@@ -1,7 +1,7 @@
#!/bin/sh
# Replace placeholders in nginx.conf
envsubst '${OPENAI_API_KEY}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
envsubst '${OPENAI_API_KEY} ${OPENAI_API_ENDPOINT} ${LLM_MODEL_NAME} ${HIDE_BUCKLE_DOT_DEV}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
# Start Nginx
nginx -g "daemon off;"

77
eslint.config.mjs Normal file
View 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',
},
},
];

20759
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,19 @@
{
"name": "chartdb",
"private": true,
"version": "1.3.1",
"version": "1.8.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"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@dbml/core": "^3.9.5",
"@dnd-kit/sortable": "^8.0.0",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.0",
@@ -28,10 +29,10 @@
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
@@ -60,7 +61,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",
@@ -69,22 +70,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
public/buckle-animated.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

BIN
public/buckle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -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';
@@ -11,6 +12,14 @@ import { DarkTheme } from './themes/dark';
import { LightTheme } from './themes/light';
import './config.ts';
export const Editor = lazy(() =>
import('./code-editor').then((module) => ({
default: module.Editor,
}))
);
type EditorType = typeof Editor;
export interface CodeSnippetProps {
className?: string;
code: string;
@@ -18,14 +27,9 @@ export interface CodeSnippetProps {
loading?: boolean;
autoScroll?: boolean;
isComplete?: boolean;
editorProps?: React.ComponentProps<EditorType>;
}
export const Editor = lazy(() =>
import('./code-editor').then((module) => ({
default: module.Editor,
}))
);
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
({
className,
@@ -34,10 +38,12 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
language = 'sql',
autoScroll = false,
isComplete = true,
editorProps,
}) => {
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 +72,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
@@ -120,27 +148,32 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
language={language}
loading={<Spinner />}
theme={effectiveTheme}
{...editorProps}
options={{
minimap: {
enabled: false,
},
readOnly: true,
automaticLayout: true,
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
alwaysConsumeMouseWheel: false,
},
scrollBeyondLastLine: false,
renderValidationDecorations: 'off',
lineDecorationsWidth: 0,
overviewRulerBorder: false,
overviewRulerLanes: 0,
hideCursorInOverviewRuler: true,
contextmenu: false,
...editorProps?.options,
guides: {
indentation: false,
...editorProps?.options?.guides,
},
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
alwaysConsumeMouseWheel: false,
...editorProps?.options?.scrollbar,
},
minimap: {
enabled: false,
...editorProps?.options?.minimap,
},
contextmenu: false,
}}
/>
{!isComplete ? (

View File

@@ -0,0 +1,54 @@
import type { Monaco } from '@monaco-editor/react';
import { dataTypes } from '@/lib/data/data-types/data-types';
export const setupDBMLLanguage = (monaco: Monaco) => {
monaco.languages.register({ id: 'dbml' });
// Define themes for DBML
monaco.editor.defineTheme('dbml-dark', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords
{ token: 'string', foreground: 'CE9178' }, // Strings
{ token: 'annotation', foreground: '9CDCFE' }, // [annotations]
{ token: 'delimiter', foreground: 'D4D4D4' }, // Braces {}
{ token: 'operator', foreground: 'D4D4D4' }, // Operators
{ token: 'datatype', foreground: '4EC9B0' }, // Data types
],
colors: {},
});
monaco.editor.defineTheme('dbml-light', {
base: 'vs',
inherit: true,
rules: [
{ token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords
{ token: 'string', foreground: 'A31515' }, // Strings
{ token: 'annotation', foreground: '001080' }, // [annotations]
{ token: 'delimiter', foreground: '000000' }, // Braces {}
{ token: 'operator', foreground: '000000' }, // Operators
{ token: 'type', foreground: '267F99' }, // Data types
],
colors: {},
});
const dataTypesNames = dataTypes.map((dt) => dt.name);
const datatypePattern = dataTypesNames.join('|');
monaco.languages.setMonarchTokensProvider('dbml', {
keywords: ['Table', 'Ref', 'Indexes'],
datatypes: dataTypesNames,
tokenizer: {
root: [
[/\b(Table|Ref|Indexes)\b/, 'keyword'],
[/\[.*?\]/, 'annotation'],
[/".*?"/, 'string'],
[/'.*?'/, 'string'],
[/[{}]/, 'delimiter'],
[/[<>]/, 'operator'],
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching
],
},
});
};

View File

@@ -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>
)

View File

@@ -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}
/>
));

View File

@@ -1,30 +1,66 @@
import React, { forwardRef } from 'react';
import EmptyStateImage from '@/assets/empty_state.png';
import EmptyStateImageDark from '@/assets/empty_state_dark.png';
import { Label } from '@/components/label/label';
import { cn } from '@/lib/utils';
import { useTheme } from '@/hooks/use-theme';
export interface EmptyStateProps {
title: string;
description: string;
imageClassName?: string;
titleClassName?: string;
descriptionClassName?: string;
}
export const EmptyState = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & EmptyStateProps
>(({ title, description, className }, ref) => (
<div
ref={ref}
className={cn(
'flex flex-1 flex-col items-center justify-center space-y-1',
className
)}
>
<img src={EmptyStateImage} alt="Empty state" className="w-32" />
<Label className="text-base">{title}</Label>
<Label className="text-sm font-normal text-muted-foreground">
{description}
</Label>
</div>
));
>(
(
{
title,
description,
className,
titleClassName,
descriptionClassName,
imageClassName,
},
ref
) => {
const { effectiveTheme } = useTheme();
return (
<div
ref={ref}
className={cn(
'flex flex-1 flex-col items-center justify-center space-y-1',
className
)}
>
<img
src={
effectiveTheme === 'dark'
? EmptyStateImageDark
: EmptyStateImage
}
alt="Empty state"
className={cn('mb-2 w-20', imageClassName)}
/>
<Label className={cn('text-base', titleClassName)}>
{title}
</Label>
<Label
className={cn(
'text-sm font-normal text-muted-foreground',
descriptionClassName
)}
>
{description}
</Label>
</div>
);
}
);
EmptyState.displayName = 'EmptyState';

View File

@@ -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',

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
import { createContext } from 'react';
import { emptyFn } from '@/lib/utils';
import type { Graph } from '@/lib/graph';
import { createGraph } from '@/lib/graph';
export interface CanvasContext {
reorderTables: (options?: { updateHistory?: boolean }) => void;
fitView: (options?: {
duration?: number;
padding?: number;
maxZoom?: number;
}) => void;
setOverlapGraph: (graph: Graph<string>) => void;
overlapGraph: Graph<string>;
}
export const canvasContext = createContext<CanvasContext>({
reorderTables: emptyFn,
fitView: emptyFn,
setOverlapGraph: emptyFn,
overlapGraph: createGraph(),
});

View File

@@ -0,0 +1,85 @@
import React, { type ReactNode, useCallback, useState } from 'react';
import { canvasContext } from './canvas-context';
import { useChartDB } from '@/hooks/use-chartdb';
import {
adjustTablePositions,
shouldShowTablesBySchemaFilter,
} from '@/lib/domain/db-table';
import { useReactFlow } from '@xyflow/react';
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
import type { Graph } from '@/lib/graph';
import { createGraph } from '@/lib/graph';
interface CanvasProviderProps {
children: ReactNode;
}
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
const { tables, relationships, updateTablesState, filteredSchemas } =
useChartDB();
const { fitView } = useReactFlow();
const [overlapGraph, setOverlapGraph] =
useState<Graph<string>>(createGraph());
const reorderTables = useCallback(
(
options: { updateHistory?: boolean } = {
updateHistory: true,
}
) => {
const newTables = adjustTablePositions({
relationships,
tables: tables.filter((table) =>
shouldShowTablesBySchemaFilter(table, filteredSchemas)
),
mode: 'all', // Use 'all' mode for manual reordering
});
const updatedOverlapGraph = findOverlappingTables({
tables: newTables,
});
updateTablesState(
(currentTables) =>
currentTables.map((table) => {
const newTable = newTables.find(
(t) => t.id === table.id
);
return {
id: table.id,
x: newTable?.x ?? table.x,
y: newTable?.y ?? table.y,
};
}),
{
updateHistory: options.updateHistory ?? true,
forceOverride: false,
}
);
setOverlapGraph(updatedOverlapGraph);
setTimeout(() => {
fitView({
duration: 500,
padding: 0.2,
maxZoom: 0.8,
});
}, 500);
},
[filteredSchemas, relationships, tables, updateTablesState, fitView]
);
return (
<canvasContext.Provider
value={{
reorderTables,
fitView,
setOverlapGraph,
overlapGraph,
}}
>
{children}
</canvasContext.Provider>
);
};

View File

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

View File

@@ -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);
@@ -1334,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);
@@ -1354,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 (
@@ -1391,6 +1400,7 @@ export const ChartDBProvider: React.FC<
updateDiagramId,
updateDiagramName,
loadDiagram,
loadDiagramFromData,
updateDatabaseType,
updateDatabaseEdition,
clearDiagramData,

View File

@@ -1,12 +1,12 @@
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';
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
export interface DialogContext {
// Create diagram dialog
@@ -21,12 +21,10 @@ export interface DialogContext {
openExportSQLDialog: (params: Omit<ExportSQLDialogProps, 'dialog'>) => void;
closeExportSQLDialog: () => void;
// Alert dialog
showAlert: (params: BaseAlertDialogProps) => void;
closeAlert: () => void;
// Create relationship dialog
openCreateRelationshipDialog: () => void;
openCreateRelationshipDialog: (
params?: Omit<CreateRelationshipDialogProps, 'dialog'>
) => void;
closeCreateRelationshipDialog: () => void;
// Import database dialog
@@ -45,6 +43,10 @@ export interface DialogContext {
openStarUsDialog: () => void;
closeStarUsDialog: () => void;
// Buckle dialog
openBuckleDialog: () => void;
closeBuckleDialog: () => void;
// Export image dialog
openExportImageDialog: (
params: Omit<ExportImageDialogProps, 'dialog'>
@@ -62,6 +64,10 @@ export interface DialogContext {
params: Omit<ImportDiagramDialogProps, 'dialog'>
) => void;
closeImportDiagramDialog: () => void;
// Import DBML dialog
openImportDBMLDialog: () => void;
closeImportDBMLDialog: () => void;
}
export const dialogContext = createContext<DialogContext>({
@@ -71,8 +77,6 @@ export const dialogContext = createContext<DialogContext>({
closeOpenDiagramDialog: emptyFn,
openExportSQLDialog: emptyFn,
closeExportSQLDialog: emptyFn,
closeAlert: emptyFn,
showAlert: emptyFn,
closeCreateRelationshipDialog: emptyFn,
openCreateRelationshipDialog: emptyFn,
openImportDatabaseDialog: emptyFn,
@@ -87,4 +91,8 @@ export const dialogContext = createContext<DialogContext>({
closeExportDiagramDialog: emptyFn,
openImportDiagramDialog: emptyFn,
closeImportDiagramDialog: emptyFn,
openBuckleDialog: emptyFn,
closeBuckleDialog: emptyFn,
openImportDBMLDialog: emptyFn,
closeImportDBMLDialog: emptyFn,
});

View File

@@ -6,8 +6,7 @@ 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 type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-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 +18,8 @@ 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';
import { ImportDBMLDialog } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -28,7 +29,19 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
useState(false);
const [createRelationshipDialogParams, setCreateRelationshipDialogParams] =
useState<Omit<CreateRelationshipDialogProps, 'dialog'>>();
const openCreateRelationshipDialogHandler: DialogContext['openCreateRelationshipDialog'] =
useCallback(
(params) => {
setCreateRelationshipDialogParams(params);
setOpenCreateRelationshipDialog(true);
},
[setOpenCreateRelationshipDialog]
);
const [openStarUsDialog, setOpenStarUsDialog] = useState(false);
const [openBuckleDialog, setOpenBuckleDialog] = useState(false);
// Export image dialog
const [openExportImageDialog, setOpenExportImageDialog] = useState(false);
@@ -88,7 +101,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
[setOpenTableSchemaDialog]
);
// Export image dialog
// Export diagram dialog
const [openExportDiagramDialog, setOpenExportDiagramDialog] =
useState(false);
@@ -96,21 +109,8 @@ 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]);
// Import DBML dialog
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
return (
<dialogContext.Provider
@@ -121,10 +121,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
openExportSQLDialog: openExportSQLDialogHandler,
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
showAlert: showAlertHandler,
closeAlert: closeAlertHandler,
openCreateRelationshipDialog: () =>
setOpenCreateRelationshipDialog(true),
openCreateRelationshipDialog:
openCreateRelationshipDialogHandler,
closeCreateRelationshipDialog: () =>
setOpenCreateRelationshipDialog(false),
openImportDatabaseDialog: openImportDatabaseDialogHandler,
@@ -134,6 +132,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),
@@ -142,6 +142,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
closeImportDiagramDialog: () =>
setOpenImportDiagramDialog(false),
openImportDBMLDialog: () => setOpenImportDBMLDialog(true),
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
}}
>
{children}
@@ -151,9 +153,9 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
dialog={{ open: openExportSQLDialog }}
{...exportSQLDialogParams}
/>
<BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} />
<CreateRelationshipDialog
dialog={{ open: openCreateRelationshipDialog }}
{...createRelationshipDialogParams}
/>
<ImportDatabaseDialog
dialog={{ open: openImportDatabaseDialog }}
@@ -170,6 +172,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
/>
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
<BuckleDialog dialog={{ open: openBuckleDialog }} />
<ImportDBMLDialog dialog={{ open: openImportDBMLDialog }} />
</dialogContext.Provider>
);
};

View File

@@ -166,6 +166,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
},
quality: 1,
pixelRatio: scale,
skipFonts: true,
});
downloadImage(dataUrl, type);

View File

@@ -9,6 +9,7 @@ import { useHistory } from '@/hooks/use-history';
import { useDialog } from '@/hooks/use-dialog';
import { useChartDB } from '@/hooks/use-chartdb';
import { useLayout } from '@/hooks/use-layout';
import { useReactFlow } from '@xyflow/react';
export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -17,6 +18,7 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
const { openOpenDiagramDialog } = useDialog();
const { updateDiagramUpdatedAt } = useChartDB();
const { toggleSidePanel } = useLayout();
const { fitView } = useReactFlow();
useHotkeys(
keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
@@ -61,6 +63,20 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
},
[toggleSidePanel]
);
useHotkeys(
keyboardShortcutsForOS[KeyboardShortcutAction.SHOW_ALL].keyCombination,
() => {
fitView({
duration: 500,
padding: 0.1,
maxZoom: 0.8,
});
},
{
preventDefault: true,
},
[fitView]
);
return (
<keyboardShortcutsContext.Provider value={{}}>

View File

@@ -6,6 +6,7 @@ export enum KeyboardShortcutAction {
OPEN_DIAGRAM = 'open_diagram',
SAVE_DIAGRAM = 'save_diagram',
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
SHOW_ALL = 'show_all',
}
export interface KeyboardShortcut {
@@ -55,6 +56,13 @@ export const keyboardShortcuts: Record<
keyCombinationMac: 'meta+b',
keyCombinationWin: 'ctrl+b',
},
[KeyboardShortcutAction.SHOW_ALL]: {
action: KeyboardShortcutAction.SHOW_ALL,
keyCombinationLabelMac: '⌘0',
keyCombinationLabelWin: 'Ctrl+0',
keyCombinationMac: 'meta+0',
keyCombinationWin: 'ctrl+0',
},
};
export interface KeyboardShortcutForOS {

View File

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

View File

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

View File

@@ -122,6 +122,32 @@ 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.version(9).upgrade((tx) =>
tx
.table<DBTable & { diagramId: string }>('db_tables')
.toCollection()
.modify((table) => {
for (const field of table.fields) {
if (typeof field.nullable === 'string') {
field.nullable =
(field.nullable as string).toLowerCase() === 'true';
}
}
})
);
db.on('ready', async () => {
const config = await getConfig();
@@ -270,6 +296,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 +388,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 ({

View File

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

View File

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

View File

@@ -85,6 +85,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const [showCheckJsonButton, setShowCheckJsonButton] = useState(false);
const [isCheckingJson, setIsCheckingJson] = useState(false);
const [showSSMSInfoDialog, setShowSSMSInfoDialog] = useState(false);
useEffect(() => {
const loadScripts = async () => {
const { importMetadataScripts } = await import(
@@ -127,6 +129,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const inputValue = e.target.value;
setScriptResult(inputValue);
// Automatically open SSMS info when input length is exactly 65535
if (inputValue.length === 65535) {
setShowSSMSInfoDialog(true);
}
},
[setScriptResult]
);
@@ -245,7 +252,10 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
{t('new_diagram_dialog.import_database.step_1')}
</div>
{databaseType === DatabaseType.SQL_SERVER && (
<SSMSInfo />
<SSMSInfo
open={showSSMSInfoDialog}
setOpen={setShowSSMSInfoDialog}
/>
)}
</div>
{databaseTypeToClientsMap[databaseType].length > 0 ? (
@@ -369,6 +379,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
showCheckJsonButton,
isCheckingJson,
handleCheckJson,
showSSMSInfoDialog,
setShowSSMSInfoDialog,
]);
const renderFooter = useCallback(() => {

View File

@@ -4,32 +4,55 @@ import {
HoverCardTrigger,
} from '@/components/hover-card/hover-card';
import { Label } from '@/components/label/label';
import { Info } from 'lucide-react';
import React from 'react';
import { Info, X } from 'lucide-react';
import React, { useCallback, useEffect, useMemo } from 'react';
import SSMSInstructions from '@/assets/ssms-instructions.png';
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
import { useTranslation } from 'react-i18next';
export interface SSMSInfoProps {}
export interface SSMSInfoProps {
open?: boolean;
setOpen?: (open: boolean) => void;
}
export const SSMSInfo = React.forwardRef<
React.ElementRef<typeof HoverCardTrigger>,
SSMSInfoProps
>((props, ref) => {
>(({ open: controlledOpen, setOpen: setControlledOpen }, ref) => {
const [open, setOpen] = React.useState(false);
const { t } = useTranslation();
useEffect(() => {
if (controlledOpen) {
setOpen(true);
}
}, [controlledOpen]);
const closeHandler = useCallback(() => {
setOpen(false);
setControlledOpen?.(false);
}, [setControlledOpen]);
const isOpen = useMemo(
() => open || controlledOpen,
[open, controlledOpen]
);
return (
<HoverCard
open={open}
open={isOpen}
onOpenChange={(isOpen) => {
if (controlledOpen) {
return;
}
setOpen(isOpen);
}}
>
<HoverCardTrigger ref={ref} {...props} asChild>
<HoverCardTrigger ref={ref} asChild>
<div
className="flex flex-row items-center gap-1 text-pink-600"
onClick={() => {
setOpen(!open);
setOpen?.(!open);
}}
>
<Info size={14} />
@@ -41,13 +64,21 @@ export const SSMSInfo = React.forwardRef<
</div>
</HoverCardTrigger>
<HoverCardContent className="w-80">
<div className="flex">
<div className="space-y-1">
<div className="flex flex-col">
<div className="flex items-start justify-between">
<h4 className="text-sm font-semibold">
{t(
'new_diagram_dialog.import_database.ssms_instructions.title'
)}
</h4>
<button
onClick={closeHandler}
className="text-muted-foreground hover:text-foreground"
>
<X size={16} />
</button>
</div>
<div className="space-y-1">
<p className="text-xs text-muted-foreground">
<span className="font-semibold">1. </span>
{t(

View File

@@ -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">

View File

@@ -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>
);

View File

@@ -22,13 +22,17 @@ import { areFieldTypesCompatible } from '@/lib/data/data-types/data-types';
const ErrorMessageRelationshipFieldsNotSameType =
'Relationships can only be created between fields of the same type';
export interface CreateRelationshipDialogProps extends BaseDialogProps {}
export interface CreateRelationshipDialogProps extends BaseDialogProps {
sourceTableId?: string;
}
export const CreateRelationshipDialog: React.FC<
CreateRelationshipDialogProps
> = ({ dialog }) => {
> = ({ dialog, sourceTableId: preSelectedSourceTableId }) => {
const { closeCreateRelationshipDialog } = useDialog();
const [primaryTableId, setPrimaryTableId] = useState<string | undefined>();
const [primaryTableId, setPrimaryTableId] = useState<string | undefined>(
preSelectedSourceTableId
);
const [primaryFieldId, setPrimaryFieldId] = useState<string | undefined>();
const [referencedTableId, setReferencedTableId] = useState<
string | undefined
@@ -43,6 +47,9 @@ export const CreateRelationshipDialog: React.FC<
const [canCreateRelationship, setCanCreateRelationship] = useState(false);
const { fitView, setEdges } = useReactFlow();
const { databaseType } = useChartDB();
const [primaryFieldSelectOpen, setPrimaryFieldSelectOpen] = useState(false);
const [referencedTableSelectOpen, setReferencedTableSelectOpen] =
useState(false);
const tableOptions = useMemo(() => {
return tables.map(
@@ -89,8 +96,23 @@ export const CreateRelationshipDialog: React.FC<
setReferencedTableId(undefined);
setReferencedFieldId(undefined);
setErrorMessage('');
setPrimaryFieldSelectOpen(false);
setReferencedTableSelectOpen(false);
}, [dialog.open]);
useEffect(() => {
if (preSelectedSourceTableId) {
const table = getTable(preSelectedSourceTableId);
if (table) {
setPrimaryTableId(preSelectedSourceTableId);
}
setTimeout(() => {
setPrimaryFieldSelectOpen(true);
}, 100);
}
}, [preSelectedSourceTableId, getTable]);
useEffect(() => {
setCanCreateRelationship(false);
setErrorMessage('');
@@ -223,8 +245,14 @@ export const CreateRelationshipDialog: React.FC<
)}
value={primaryTableId}
onChange={(value) => {
setPrimaryTableId(value as string);
setPrimaryFieldId(undefined);
const newTableId = value as string;
setPrimaryTableId(newTableId);
if (
newTableId !==
preSelectedSourceTableId
) {
setPrimaryFieldId(undefined);
}
}}
emptyPlaceholder={t(
'create_relationship_dialog.no_tables_found'
@@ -253,6 +281,8 @@ export const CreateRelationshipDialog: React.FC<
'create_relationship_dialog.primary_field_placeholder'
)}
value={primaryFieldId}
open={primaryFieldSelectOpen}
onOpenChange={setPrimaryFieldSelectOpen}
onChange={(value) =>
setPrimaryFieldId(value as string)
}
@@ -283,6 +313,8 @@ export const CreateRelationshipDialog: React.FC<
'create_relationship_dialog.referenced_table_placeholder'
)}
value={referencedTableId}
open={referencedTableSelectOpen}
onOpenChange={setReferencedTableSelectOpen}
onChange={(value) => {
setReferencedTableId(value as string);
setReferencedFieldId(undefined);

View File

@@ -20,10 +20,12 @@ import {
} from '@/lib/data/export-metadata/export-sql-script';
import { databaseTypeToLabelMap } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type';
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
import { Annoyed, Sparkles } from 'lucide-react';
import React, { useCallback, useEffect, useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import type { BaseDialogProps } from '../common/base-dialog-props';
import type { Diagram } from '@/lib/domain/diagram';
export interface ExportSQLDialogProps extends BaseDialogProps {
targetDatabaseType: DatabaseType;
@@ -34,7 +36,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
targetDatabaseType,
}) => {
const { closeExportSQLDialog } = useDialog();
const { currentDiagram } = useChartDB();
const { currentDiagram, filteredSchemas } = useChartDB();
const { t } = useTranslation();
const [script, setScript] = React.useState<string>();
const [error, setError] = React.useState<boolean>(false);
@@ -43,17 +45,58 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const abortControllerRef = useRef<AbortController | null>(null);
const exportSQLScript = useCallback(async () => {
const filteredDiagram: Diagram = {
...currentDiagram,
tables: currentDiagram.tables?.filter((table) =>
shouldShowTablesBySchemaFilter(table, filteredSchemas)
),
relationships: currentDiagram.relationships?.filter((rel) => {
const sourceTable = currentDiagram.tables?.find(
(t) => t.id === rel.sourceTableId
);
const targetTable = currentDiagram.tables?.find(
(t) => t.id === rel.targetTableId
);
return (
sourceTable &&
targetTable &&
shouldShowTablesBySchemaFilter(
sourceTable,
filteredSchemas
) &&
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
);
}),
dependencies: currentDiagram.dependencies?.filter((dep) => {
const table = currentDiagram.tables?.find(
(t) => t.id === dep.tableId
);
const dependentTable = currentDiagram.tables?.find(
(t) => t.id === dep.dependentTableId
);
return (
table &&
dependentTable &&
shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
shouldShowTablesBySchemaFilter(
dependentTable,
filteredSchemas
)
);
}),
};
if (targetDatabaseType === DatabaseType.GENERIC) {
return Promise.resolve(exportBaseSQL(currentDiagram));
return Promise.resolve(exportBaseSQL(filteredDiagram));
} else {
return exportSQL(currentDiagram, targetDatabaseType, {
return exportSQL(filteredDiagram, targetDatabaseType, {
stream: true,
onResultStream: (text) =>
setScript((prev) => (prev ? prev + text : text)),
signal: abortControllerRef.current?.signal,
});
}
}, [targetDatabaseType, currentDiagram]);
}, [targetDatabaseType, currentDiagram, filteredSchemas]);
useEffect(() => {
if (!dialog.open) {
@@ -70,7 +113,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const script = await exportSQLScript();
setScript(script);
setIsScriptLoading(false);
} catch (e) {
} catch {
setError(true);
}
};

View File

@@ -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';
@@ -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,
@@ -30,6 +32,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
addTables,
addRelationships,
diagramName,
databaseType: currentDatabaseType,
updateDatabaseType,
} = useChartDB();
const [scriptResult, setScriptResult] = useState('');
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
@@ -282,6 +286,10 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
}),
]);
if (currentDatabaseType === DatabaseType.GENERIC) {
await updateDatabaseType(databaseType);
}
setNodes((nodes) =>
nodes.map((node) => ({
...node,
@@ -297,6 +305,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
closeImportDatabaseDialog();
}, [
databaseEdition,
currentDatabaseType,
updateDatabaseType,
databaseType,
scriptResult,
tables,

View File

@@ -0,0 +1,399 @@
import React, {
useCallback,
useEffect,
useState,
Suspense,
useRef,
} from 'react';
import * as monaco from 'monaco-editor';
import { useDialog } from '@/hooks/use-dialog';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { Button } from '@/components/button/button';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useTranslation } from 'react-i18next';
import { Editor } from '@/components/code-snippet/code-snippet';
import { useTheme } from '@/hooks/use-theme';
import { AlertCircle } from 'lucide-react';
import { importDBMLToDiagram } from '@/lib/dbml-import';
import { useChartDB } from '@/hooks/use-chartdb';
import { Parser } from '@dbml/core';
import { useCanvas } from '@/hooks/use-canvas';
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
import { useToast } from '@/components/toast/use-toast';
import { Spinner } from '@/components/spinner/spinner';
import { debounce } from '@/lib/utils';
interface DBMLError {
message: string;
line: number;
column: number;
}
function parseDBMLError(error: unknown): DBMLError | null {
try {
if (typeof error === 'string') {
const parsed = JSON.parse(error);
if (parsed.diags?.[0]) {
const diag = parsed.diags[0];
return {
message: diag.message,
line: diag.location.start.line,
column: diag.location.start.column,
};
}
} else if (error && typeof error === 'object' && 'diags' in error) {
const parsed = error as {
diags: Array<{
message: string;
location: { start: { line: number; column: number } };
}>;
};
if (parsed.diags?.[0]) {
return {
message: parsed.diags[0].message,
line: parsed.diags[0].location.start.line,
column: parsed.diags[0].location.start.column,
};
}
}
} catch (e) {
console.error('Error parsing DBML error:', e);
}
return null;
}
export interface ImportDBMLDialogProps extends BaseDialogProps {}
export const ImportDBMLDialog: React.FC<ImportDBMLDialogProps> = ({
dialog,
}) => {
const { t } = useTranslation();
const initialDBML = `// Use DBML to define your database structure
// Simple Blog System with Comments Example
Table users {
id integer [primary key]
name varchar
email varchar
}
Table posts {
id integer [primary key]
title varchar
content text
user_id integer
created_at timestamp
}
Table comments {
id integer [primary key]
content text
post_id integer
user_id integer
created_at timestamp
}
// Relationships
Ref: posts.user_id > users.id // Each post belongs to one user
Ref: comments.post_id > posts.id // Each comment belongs to one post
Ref: comments.user_id > users.id // Each comment is written by one user`;
const [dbmlContent, setDBMLContent] = useState<string>(initialDBML);
const { closeImportDBMLDialog } = useDialog();
const [errorMessage, setErrorMessage] = useState<string | undefined>();
const { effectiveTheme } = useTheme();
const { toast } = useToast();
const {
addTables,
addRelationships,
tables,
relationships,
removeTables,
removeRelationships,
} = useChartDB();
const { reorderTables } = useCanvas();
const [reorder, setReorder] = useState(false);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
const decorationsCollection =
useRef<monaco.editor.IEditorDecorationsCollection>();
const handleEditorDidMount = (
editor: monaco.editor.IStandaloneCodeEditor
) => {
editorRef.current = editor;
decorationsCollection.current = editor.createDecorationsCollection();
};
useEffect(() => {
if (reorder) {
reorderTables({
updateHistory: false,
});
setReorder(false);
}
}, [reorder, reorderTables]);
const highlightErrorLine = useCallback((error: DBMLError) => {
if (!editorRef.current) return;
const model = editorRef.current.getModel();
if (!model) return;
const decorations = [
{
range: new monaco.Range(
error.line,
1,
error.line,
model.getLineMaxColumn(error.line)
),
options: {
isWholeLine: true,
className: 'dbml-error-line',
glyphMarginClassName: 'dbml-error-glyph',
hoverMessage: { value: error.message },
overviewRuler: {
color: '#ff0000',
position: monaco.editor.OverviewRulerLane.Right,
darkColor: '#ff0000',
},
},
},
];
decorationsCollection.current?.set(decorations);
}, []);
const clearDecorations = useCallback(() => {
decorationsCollection.current?.clear();
}, []);
const validateDBML = useCallback(
async (content: string) => {
// Clear previous errors
setErrorMessage(undefined);
clearDecorations();
if (!content.trim()) return;
try {
const parser = new Parser();
parser.parse(content, 'dbml');
} catch (e) {
const parsedError = parseDBMLError(e);
if (parsedError) {
setErrorMessage(
t('import_dbml_dialog.error.description') +
` (1 error found - in line ${parsedError.line})`
);
highlightErrorLine(parsedError);
} else {
setErrorMessage(
e instanceof Error ? e.message : JSON.stringify(e)
);
}
}
},
[clearDecorations, highlightErrorLine, t]
);
const debouncedValidateRef = useRef<((value: string) => void) | null>(null);
// Set up debounced validation
useEffect(() => {
debouncedValidateRef.current = debounce((value: string) => {
validateDBML(value);
}, 500);
return () => {
debouncedValidateRef.current = null;
};
}, [validateDBML]);
// Trigger validation when content changes
useEffect(() => {
if (debouncedValidateRef.current) {
debouncedValidateRef.current(dbmlContent);
}
}, [dbmlContent]);
useEffect(() => {
if (!dialog.open) {
setErrorMessage(undefined);
clearDecorations();
setDBMLContent(initialDBML);
}
}, [dialog.open, initialDBML, clearDecorations]);
const handleImport = useCallback(async () => {
if (!dbmlContent.trim() || errorMessage) return;
try {
const importedDiagram = await importDBMLToDiagram(dbmlContent);
const tableIdsToRemove = tables
.filter((table) =>
importedDiagram.tables?.some(
(t) =>
t.name === table.name && t.schema === table.schema
)
)
.map((table) => table.id);
// Find relationships that need to be removed
const relationshipIdsToRemove = relationships
.filter((relationship) => {
const sourceTable = tables.find(
(table) => table.id === relationship.sourceTableId
);
const targetTable = tables.find(
(table) => table.id === relationship.targetTableId
);
if (!sourceTable || !targetTable) return true;
const replacementSourceTable = importedDiagram.tables?.find(
(table) =>
table.name === sourceTable.name &&
table.schema === sourceTable.schema
);
const replacementTargetTable = importedDiagram.tables?.find(
(table) =>
table.name === targetTable.name &&
table.schema === targetTable.schema
);
return replacementSourceTable || replacementTargetTable;
})
.map((relationship) => relationship.id);
// Remove existing items
await Promise.all([
removeTables(tableIdsToRemove, { updateHistory: false }),
removeRelationships(relationshipIdsToRemove, {
updateHistory: false,
}),
]);
// Add new items
await Promise.all([
addTables(importedDiagram.tables ?? [], {
updateHistory: false,
}),
addRelationships(importedDiagram.relationships ?? [], {
updateHistory: false,
}),
]);
setReorder(true);
closeImportDBMLDialog();
} catch (e) {
toast({
title: t('import_dbml_dialog.error.title'),
variant: 'destructive',
description: (
<>
<div>{t('import_dbml_dialog.error.description')}</div>
{e instanceof Error ? e.message : JSON.stringify(e)}
</>
),
});
}
}, [
dbmlContent,
closeImportDBMLDialog,
tables,
relationships,
removeTables,
removeRelationships,
addTables,
addRelationships,
errorMessage,
toast,
setReorder,
t,
]);
return (
<Dialog
{...dialog}
onOpenChange={(open) => {
if (!open) {
closeImportDBMLDialog();
}
}}
>
<DialogContent
className="flex h-[80vh] max-h-screen flex-col"
showClose
>
<DialogHeader>
<DialogTitle>{t('import_dbml_dialog.title')}</DialogTitle>
<DialogDescription>
{t('import_dbml_dialog.description')}
</DialogDescription>
</DialogHeader>
<DialogInternalContent>
<Suspense fallback={<Spinner />}>
<Editor
value={dbmlContent}
onChange={(value) => setDBMLContent(value || '')}
language="dbml"
onMount={handleEditorDidMount}
theme={
effectiveTheme === 'dark'
? 'dbml-dark'
: 'dbml-light'
}
beforeMount={setupDBMLLanguage}
options={{
minimap: { enabled: false },
scrollBeyondLastLine: false,
automaticLayout: true,
glyphMargin: true,
lineNumbers: 'on',
scrollbar: {
vertical: 'visible',
horizontal: 'visible',
},
}}
className="size-full"
/>
</Suspense>
</DialogInternalContent>
<DialogFooter>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-4">
<DialogClose asChild>
<Button variant="secondary">
{t('import_dbml_dialog.cancel')}
</Button>
</DialogClose>
{errorMessage ? (
<div className="flex items-center gap-1">
<AlertCircle className="size-4 text-destructive" />
<span className="text-xs text-destructive">
{errorMessage ||
t(
'import_dbml_dialog.error.description'
)}
</span>
</div>
) : null}
</div>
<Button
onClick={handleImport}
disabled={!dbmlContent.trim() || !!errorMessage}
>
{t('import_dbml_dialog.import')}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -22,10 +22,11 @@ import { useConfig } from '@/hooks/use-config';
import { useDialog } from '@/hooks/use-dialog';
import { useStorage } from '@/hooks/use-storage';
import type { Diagram } from '@/lib/domain/diagram';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useDebounce } from '@/hooks/use-debounce';
export interface OpenDiagramDialogProps extends BaseDialogProps {}
@@ -58,12 +59,65 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
fetchDiagrams();
}, [listDiagrams, setDiagrams, dialog.open]);
const openDiagram = (diagramId: string) => {
if (diagramId) {
updateConfig({ defaultDiagramId: diagramId });
navigate(`/diagrams/${diagramId}`);
}
};
const openDiagram = useCallback(
(diagramId: string) => {
if (diagramId) {
updateConfig({ defaultDiagramId: diagramId });
navigate(`/diagrams/${diagramId}`);
}
},
[updateConfig, navigate]
);
const handleRowKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLTableRowElement>) => {
const element = e.target as HTMLElement;
const diagramId = element.getAttribute('data-diagram-id');
const selectionIndexAttr = element.getAttribute(
'data-selection-index'
);
if (!diagramId || !selectionIndexAttr) return;
const selectionIndex = parseInt(selectionIndexAttr, 10);
switch (e.key) {
case 'Enter':
case ' ':
e.preventDefault();
openDiagram(diagramId);
closeOpenDiagramDialog();
break;
case 'ArrowDown': {
e.preventDefault();
(
document.querySelector(
`[data-selection-index="${selectionIndex + 1}"]`
) as HTMLElement
)?.focus();
break;
}
case 'ArrowUp': {
e.preventDefault();
(
document.querySelector(
`[data-selection-index="${selectionIndex - 1}"]`
) as HTMLElement
)?.focus();
break;
}
}
},
[openDiagram, closeOpenDiagramDialog]
);
const onFocusHandler = useDebounce(
(diagramId: string) => setSelectedDiagramId(diagramId),
50
);
return (
<Dialog
{...dialog}
@@ -74,7 +128,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>
@@ -112,10 +166,17 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
</TableRow>
</TableHeader>
<TableBody>
{diagrams.map((diagram) => (
{diagrams.map((diagram, index) => (
<TableRow
key={diagram.id}
data-state={`${selectedDiagramId === diagram.id ? 'selected' : ''}`}
data-diagram-id={diagram.id}
data-selection-index={index}
tabIndex={0}
onFocus={() =>
onFocusHandler(diagram.id)
}
className="focus:bg-accent focus:outline-none"
onClick={(e) => {
switch (e.detail) {
case 1:
@@ -133,11 +194,17 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
);
}
}}
onKeyDown={handleRowKeyDown}
>
<TableCell className="table-cell">
<div className="flex justify-center">
<DiagramIcon
diagram={diagram}
databaseType={
diagram.databaseType
}
databaseEdition={
diagram.databaseEdition
}
/>
</div>
</TableCell>

View File

@@ -73,3 +73,68 @@
@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;
}
.dbml-error-line {
background-color: rgba(255, 0, 0, 0.2) !important;
}
@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%;
}
}

4
src/hooks/use-canvas.ts Normal file
View File

@@ -0,0 +1,4 @@
import { useContext } from 'react';
import { canvasContext } from '@/context/canvas-context/canvas-context';
export const useCanvas = () => useContext(canvasContext);

21
src/hooks/use-debounce.ts Normal file
View File

@@ -0,0 +1,21 @@
import { useCallback, useRef } from 'react';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyFunction = (...args: any[]) => any;
export const useDebounce = <T extends AnyFunction>(
func: T,
delay: number
): ((...args: Parameters<T>) => void) => {
const inDebounce = useRef<NodeJS.Timeout>();
const debounce = useCallback(
(...args: Parameters<T>) => {
clearTimeout(inDebounce.current);
inDebounce.current = setTimeout(() => func(...args), delay);
},
[func, delay]
);
return debounce;
};

View File

@@ -19,8 +19,10 @@ 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';
import { ar, arMetadata } from './locales/ar';
export const languages: LanguageMetadata[] = [
enMetadata,
@@ -40,8 +42,10 @@ export const languages: LanguageMetadata[] = [
trMetadata,
id_IDMetadata,
teMetadata,
bnMetadata,
guMetadata,
viMetadata,
arMetadata,
];
const resources = {
@@ -62,8 +66,10 @@ const resources = {
tr,
id_ID,
te,
bn,
gu,
vi,
ar,
};
i18n.use(LanguageDetector)

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

@@ -0,0 +1,424 @@
import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ar: LanguageTranslation = {
translation: {
menu: {
file: {
file: 'ملف',
new: 'جديد',
open: 'فتح',
save: 'حفظ',
import: 'استيراد قاعدة بيانات',
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: 'طي الكل',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
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 الرسم البياني',
},
},
import_dbml_dialog: {
// TODO: Translate
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
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: 'حذف الجدول',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: '({{key}} مغنظة الشبكة (اضغط مع الاستمرار على',
tool_tips: {
double_click_to_edit: 'انقر مرتين للتعديل',
},
language_select: {
change_language: 'اللغة',
},
},
};
export const arMetadata: LanguageMetadata = {
name: 'Arabic',
nativeName: 'العربية',
code: 'ar',
};

428
src/i18n/locales/bn.ts Normal file
View File

@@ -0,0 +1,428 @@
import type { LanguageMetadata, LanguageTranslation } from '../types';
export const bn: LanguageTranslation = {
translation: {
menu: {
file: {
file: 'ফাইল',
new: 'নতুন',
open: 'খুলুন',
save: 'সংরক্ষণ করুন',
import: 'ডাটাবেস আমদানি করুন',
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:
'{{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: 'সংরক্ষিত',
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: 'সব ভাঁজ করুন',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
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-এ যোগাযোগ করুন।',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
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: 'টেবিল মুছে ফেলুন',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'গ্রিডে স্ন্যাপ করুন (অবস্থান {{key}})',
tool_tips: {
double_click_to_edit: 'সম্পাদনা করতে ডাবল-ক্লিক করুন',
},
language_select: {
change_language: 'ভাষা পরিবর্তন করুন',
},
},
};
export const bnMetadata: LanguageMetadata = {
name: 'Bengali',
nativeName: 'বাংলা',
code: 'bn',
};

View File

@@ -8,7 +8,7 @@ export const de: LanguageTranslation = {
new: 'Neu',
open: 'Öffnen',
save: 'Speichern',
import_database: 'Datenbank importieren',
import: 'Datenbank importieren',
export_sql: 'SQL exportieren',
export_as: 'Exportieren als',
delete_diagram: 'Diagramm löschen',
@@ -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: {
@@ -78,6 +81,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',
@@ -91,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',
@@ -112,6 +126,12 @@ export const de: LanguageTranslation = {
add_table: 'Tabelle hinzufügen',
filter: 'Filter',
collapse: 'Alle einklappen',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'Felder',
@@ -362,6 +382,17 @@ export const de: LanguageTranslation = {
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
relationship_type: {
one_to_one: 'Ein zu Eins (1:1)',
one_to_many: 'Ein zu Viele (1:n)',
@@ -378,6 +409,7 @@ export const de: LanguageTranslation = {
edit_table: 'Tabelle bearbeiten',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Tabelle löschen',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations

View File

@@ -8,7 +8,7 @@ export const en = {
new: 'New',
open: 'Open',
save: 'Save',
import_database: 'Import Database',
import: 'Import',
export_sql: 'Export SQL',
export_as: 'Export as',
delete_diagram: 'Delete Diagram',
@@ -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',
@@ -77,6 +79,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',
@@ -90,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',
@@ -111,6 +123,10 @@ export const en = {
add_table: 'Add Table',
filter: 'Filter',
collapse: 'Collapse All',
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'Fields',
@@ -349,7 +365,7 @@ export const en = {
import_diagram_dialog: {
title: 'Import Diagram',
description: 'Paste the diagram JSON below:',
description: 'Import a diagram from a JSON file.',
cancel: 'Cancel',
import: 'Import',
error: {
@@ -358,6 +374,17 @@ export const en = {
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error importing DBML',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
relationship_type: {
one_to_one: 'One to One',
one_to_many: 'One to Many',
@@ -374,6 +401,7 @@ export const en = {
edit_table: 'Edit Table',
duplicate_table: 'Duplicate Table',
delete_table: 'Delete Table',
add_relationship: 'Add Relationship',
},
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',

View File

@@ -8,7 +8,7 @@ export const es: LanguageTranslation = {
new: 'Nuevo',
open: 'Abrir',
save: 'Guardar',
import_database: 'Importar Base de Datos',
import: 'Importar Base de Datos',
export_sql: 'Exportar SQL',
export_as: 'Exportar como',
delete_diagram: 'Eliminar Diagrama',
@@ -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: {
@@ -69,6 +72,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',
@@ -82,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',
@@ -103,6 +116,12 @@ export const es: LanguageTranslation = {
add_table: 'Agregar Tabla',
filter: 'Filtrar',
collapse: 'Colapsar Todo',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'Campos',
@@ -362,6 +381,17 @@ export const es: LanguageTranslation = {
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
relationship_type: {
one_to_one: 'Uno a Uno',
one_to_many: 'Uno a Muchos',
@@ -378,6 +408,7 @@ export const es: LanguageTranslation = {
edit_table: 'Editar Tabla',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Eliminar Tabla',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations

View File

@@ -8,7 +8,7 @@ export const fr: LanguageTranslation = {
new: 'Nouveau',
open: 'Ouvrir',
save: 'Enregistrer',
import_database: 'Importer Base de Données',
import: 'Importer Base de Données',
export_sql: 'Exporter SQL',
export_as: 'Exporter en tant que',
delete_diagram: 'Supprimer le Diagramme',
@@ -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',
@@ -68,6 +71,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',
@@ -81,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',
@@ -103,6 +116,12 @@ export const fr: LanguageTranslation = {
add_table: 'Ajouter une Table',
filter: 'Filtrer',
collapse: 'Réduire Tout',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'Champs',
@@ -364,6 +383,17 @@ export const fr: LanguageTranslation = {
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
relationship_type: {
one_to_one: 'Un à Un',
one_to_many: 'Un à Plusieurs',
@@ -380,6 +410,7 @@ export const fr: LanguageTranslation = {
edit_table: 'Éditer la Table',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Supprimer la Table',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations

View File

@@ -8,7 +8,7 @@ export const gu: LanguageTranslation = {
new: 'નવું',
open: 'ખોલો',
save: 'સાચવો',
import_database: 'ડેટાબેસ આયાત કરો',
import: 'ડેટાબેસ આયાત કરો',
export_sql: 'SQL નિકાસ કરો',
export_as: 'રૂપે નિકાસ કરો',
delete_diagram: 'ડાયાગ્રામ કાઢી નાખો',
@@ -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: {
@@ -78,6 +81,17 @@ export const gu: LanguageTranslation = {
none: 'કઈ નહીં',
},
copy_to_clipboard_toast: {
unsupported: {
title: 'નકલ નિષ્ફળ',
description: 'ક્લિપબોર્ડ આધારિત નથી',
},
failed: {
title: 'નકલ નિષ્ફળ',
description: 'કંઈક ખોટું થયું છે. કૃપા કરીને ફરી પ્રયાસ કરો.',
},
},
theme: {
system: 'સિસ્ટમ',
light: 'હલકો',
@@ -91,7 +105,6 @@ export const gu: LanguageTranslation = {
last_saved: 'છેલ્લે સાચવ્યું',
saved: 'સાચવ્યું',
diagrams: 'ડાયાગ્રામ',
loading_diagram: 'ડાયાગ્રામ લોડ થઈ રહ્યું છે...',
deselect_all: 'બધાને ડીસેલેક્ટ કરો',
select_all: 'બધા પસંદ કરો',
@@ -112,6 +125,12 @@ export const gu: LanguageTranslation = {
add_table: 'ટેબલ ઉમેરો',
filter: 'ફિલ્ટર',
collapse: 'બધાને સકુચિત કરો',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'ફીલ્ડ્સ',
@@ -360,6 +379,17 @@ export const gu: LanguageTranslation = {
'ડાયાગ્રામ JSON અમાન્ય છે. કૃપા કરીને JSON તપાસો અને ફરી પ્રયાસ કરો. મદદ જોઈએ? chartdb.io@gmail.com પર સંપર્ક કરો.',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
relationship_type: {
one_to_one: 'એકથી એક',
one_to_many: 'એકથી ઘણા',
@@ -376,6 +406,7 @@ export const gu: LanguageTranslation = {
edit_table: 'ટેબલ સંપાદિત કરો',
duplicate_table: 'ટેબલ નકલ કરો',
delete_table: 'ટેબલ કાઢી નાખો',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'ગ્રિડ પર સ્નેપ કરો (જમાવટ {{key}})',

View File

@@ -8,7 +8,7 @@ export const hi: LanguageTranslation = {
new: 'नया',
open: 'खोलें',
save: 'सहेजें',
import_database: 'डेटाबेस आयात करें',
import: 'डेटाबेस आयात करें',
export_sql: 'SQL निर्यात करें',
export_as: 'के रूप में निर्यात करें',
delete_diagram: 'आरेख हटाएँ',
@@ -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: {
@@ -78,6 +81,17 @@ export const hi: LanguageTranslation = {
none: 'कोई नहीं',
},
copy_to_clipboard_toast: {
unsupported: {
title: 'कॉपी असफल',
description: 'क्लिपबोर्ड समर्थित नहीं है',
},
failed: {
title: 'कॉपी असफल',
description: 'कुछ गलत हो गया। कृपया पुनः प्रयास करें।',
},
},
theme: {
system: 'सिस्टम',
light: 'हल्का',
@@ -91,7 +105,6 @@ export const hi: LanguageTranslation = {
last_saved: 'अंतिम सहेजा गया',
saved: 'सहेजा गया',
diagrams: 'आरेख',
loading_diagram: 'आरेख लोड हो रहा है...',
deselect_all: 'सभी को अचयनित करें',
select_all: 'सभी को चुनें',
@@ -113,6 +126,12 @@ export const hi: LanguageTranslation = {
add_table: 'तालिका जोड़ें',
filter: 'फ़िल्टर',
collapse: 'सभी को संक्षिप्त करें',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'फ़ील्ड्स',
@@ -364,6 +383,17 @@ export const hi: LanguageTranslation = {
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
relationship_type: {
one_to_one: 'एक से एक',
one_to_many: 'एक से कई',
@@ -380,6 +410,7 @@ export const hi: LanguageTranslation = {
edit_table: 'तालिका संपादित करें',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'तालिका हटाएँ',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations

View File

@@ -8,7 +8,7 @@ export const id_ID: LanguageTranslation = {
new: 'Buat Baru',
open: 'Buka',
save: 'Simpan',
import_database: 'Impor Database',
import: 'Impor Database',
export_sql: 'Ekspor SQL',
export_as: 'Ekspor Sebagai',
delete_diagram: 'Hapus Diagram',
@@ -30,12 +30,14 @@ 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',
},
// 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 +80,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',
@@ -91,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',
@@ -112,6 +124,12 @@ export const id_ID: LanguageTranslation = {
add_table: 'Tambah Tabel',
filter: 'Saring',
collapse: 'Lipat Semua',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'Kolom',
@@ -335,30 +353,39 @@ 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',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
@@ -377,16 +404,14 @@ 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',
add_relationship: 'Add Relationship', // TODO: Translate
},
// 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: {

View File

@@ -8,7 +8,7 @@ export const ja: LanguageTranslation = {
new: '新規',
open: '開く',
save: '保存',
import_database: 'データベースをインポート',
import: 'データベースをインポート',
export_sql: 'SQLをエクスポート',
export_as: '形式を指定してエクスポート',
delete_diagram: 'ダイアグラムを削除',
@@ -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: {
@@ -79,6 +82,18 @@ export const ja: LanguageTranslation = {
none: 'なし',
},
copy_to_clipboard_toast: {
unsupported: {
title: 'コピー失敗',
description: 'クリップボードがサポートされていません',
},
failed: {
title: 'コピー失敗',
description:
'何かがうまくいきませんでした。もう一度お試しください。',
},
},
theme: {
system: 'システム',
light: 'ライト',
@@ -92,7 +107,6 @@ export const ja: LanguageTranslation = {
last_saved: '最後に保存された',
saved: '保存されました',
diagrams: 'ダイアグラム',
loading_diagram: 'ダイアグラムを読み込み中...',
deselect_all: 'すべての選択を解除',
select_all: 'すべてを選択',
@@ -114,6 +128,12 @@ export const ja: LanguageTranslation = {
add_table: 'テーブルを追加',
filter: 'フィルタ',
collapse: 'すべて折りたたむ',
// TODO: Translate
clear: 'Clear Filter',
no_results: 'No tables found matching your filter.',
// TODO: Translate
show_list: 'Show Table List',
show_dbml: 'Show DBML Editor',
table: {
fields: 'フィールド',
@@ -366,6 +386,17 @@ export const ja: LanguageTranslation = {
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_dbml_dialog: {
title: 'Import DBML',
description: 'Import a database schema from DBML format.',
import: 'Import',
cancel: 'Cancel',
error: {
title: 'Error',
description: 'Failed to parse DBML. Please check the syntax.',
},
},
relationship_type: {
one_to_one: '1対1',
one_to_many: '1対多',
@@ -382,6 +413,7 @@ export const ja: LanguageTranslation = {
edit_table: 'テーブルを編集',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'テーブルを削除',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations

Some files were not shown because too many files have changed in this diff Show More