Compare commits

..

105 Commits

Author SHA1 Message Date
Guy Ben-Aharon
9baecea4ab fix: note markdown empty note (#975) 2025-11-04 14:52:46 +02:00
Guy Ben-Aharon
c8b827764c fix: note markdown empty note (#974) 2025-11-04 12:53:07 +02:00
Guy Ben-Aharon
0afa71efcc fix: adjust relationship edge offset when cardinality is visible (#973) 2025-11-04 11:51:06 +02:00
Guy Ben-Aharon
69d4e8dca6 fix: disable dragging on edit node content (#972) 2025-11-04 11:45:09 +02:00
Guy Ben-Aharon
a4674a2bf8 feat: add markdown support to sticky notes (#971) 2025-11-04 11:35:53 +02:00
Jonathan Fishner
07dc4eace0 fix: Add Transactional/Analytical database categorization tabs (#965)
* fix: Add Transactional/Analytical database categorization tabs

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-11-03 12:03:25 +02:00
Guy Ben-Aharon
4fd940afbb fix: notes colors (#970) 2025-11-02 20:40:08 +02:00
Guy Ben-Aharon
3d85bcc6ab fix: notes with readonly (#969) 2025-11-02 19:18:58 +02:00
Guy Ben-Aharon
973b7663b1 fix: dbml with notes (#968) 2025-11-02 18:07:53 +02:00
Guy Ben-Aharon
6d38ebe3ec feat: add sticky notes (#967)
* feat: add sticky notes

* feat: add sticky notes
2025-11-02 15:42:18 +02:00
Jonathan Fishner
68412f90a7 fix: add postgres array type support for import and export (#958)
* fix: add postgres array type support for import and export

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-10-27 17:02:44 +02:00
Guy Ben-Aharon
084a1d505c chore(main): release 1.17.0 (#936) 2025-10-27 16:27:46 +02:00
Guy Ben-Aharon
91e713c30a fix: import array fields (#961) 2025-10-27 11:55:11 +02:00
Guy Ben-Aharon
acf6d4b365 fix: show SQL Script option conditionally for databases without DDL support (#960) 2025-10-27 11:24:23 +02:00
Guy Ben-Aharon
9e8979d062 update translate (#957) 2025-10-21 15:44:49 +03:00
Guy Ben-Aharon
9ed27cf30c fix: preserve multi-word types in DBML export/import (#956)
* fix: preserve multi-word types in DBML export/import

* fix
2025-10-21 15:10:02 +03:00
Guy Ben-Aharon
2c4b344efb fix: resolve dbml increment & nullable attributes issue (#954)
* fix: resolve dbml increment attribute

* fix nullable

* fix
2025-10-21 12:31:32 +03:00
Guy Ben-Aharon
ccb29e0a57 fix: resolve canvas filter tree state issues (#953) 2025-10-20 17:12:15 +03:00
Guy Ben-Aharon
7d811de097 fix: add open table in editor from canvas edit (#952) 2025-10-20 15:56:04 +03:00
Guy Ben-Aharon
62dec48572 fix: use flag for custom types (#951) 2025-10-19 18:54:08 +03:00
Jonathan Fishner
49328d8fbd fix: add support for arrays (#949)
* feat: add array field support with diff visualization

* some refactor

* fix

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-10-19 17:47:39 +03:00
Jonathan Fishner
459698b5d0 fix: add support for parsing default values in DBML (#948) 2025-10-16 21:07:55 +03:00
Guy Ben-Aharon
7ad0e7712d fix: manipulate schema directly from the canvas (#947) 2025-10-16 17:37:20 +03:00
Guy Ben-Aharon
34475add32 feat: create relationships on canvas modal (#946)
* feat: create relationships on canvas modal

* feat: create relationships on canvas modal

* feat: create relationships on canvas modal

* fix

* fix

* fix

* fix
2025-10-13 18:08:28 +03:00
Guy Ben-Aharon
38fedcec0c fix: exit table edit on area click (#945)
* fix: exit table edit on area click

* fix
2025-10-10 19:38:43 +03:00
Guy Ben-Aharon
498655e7b7 fix: prevent text input glitch when editing table field names (#944) 2025-10-10 15:22:09 +03:00
Jonathan Fishner
bcd8aa9378 fix: auto-enter edit mode when creating new tables from canvas (#943)
* fix: auto-enter edit mode when creating new tables from canvas or sidebar

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-10-09 15:43:41 +03:00
Jonathan Fishner
b15bc945ac fix: add timestampz and int as datatypes to postgres (#940) 2025-10-09 15:05:33 +03:00
Guy Ben-Aharon
c3c646bf7c fix: add rels export dbml (#937) 2025-09-28 20:27:41 +03:00
Jonathan Fishner
57b3b8777f fix: add auto-increment field detection in smart-query import (#935) 2025-09-28 17:09:02 +03:00
Guy Ben-Aharon
bb033091b1 fix: dbml diff fields types preview (#934) 2025-09-28 17:02:56 +03:00
Guy Ben-Aharon
c9ac8929c5 chore(main): release 1.16.0 (#881) 2025-09-27 15:57:34 +03:00
Guy Ben-Aharon
c567c0a5f3 fix: remove many to many rel option (#933) 2025-09-24 14:14:49 +03:00
Guy Ben-Aharon
2dc1a6fc75 fix: move area utils (#932) 2025-09-23 11:28:08 +03:00
Guy Ben-Aharon
98f6edd5c8 fix: add areas width and height + table width to diff check (#931)
* fix: add areas width and height + table width to diff check

* fix
2025-09-23 11:12:46 +03:00
Guy Ben-Aharon
47a7a73a13 fix: add tests for diff (#930)
* fix: add tests for diff

* fix
2025-09-21 18:34:29 +03:00
Guy Ben-Aharon
d71b46e8b5 move utils to dir (#929) 2025-09-21 16:58:12 +03:00
Guy Ben-Aharon
e4c4a3b354 fix: add diff x,y (#928) 2025-09-21 12:12:20 +03:00
Guy Ben-Aharon
1b8d51b73c fix: diff logic (#927) 2025-09-20 19:40:04 +03:00
Guy Ben-Aharon
93d72a896b fix: dbml edit mode glitch (#925) 2025-09-17 20:23:42 +03:00
Guy Ben-Aharon
9991077978 fix: handle bidirectional relationships in DBML export (#924) 2025-09-17 18:13:48 +03:00
Guy Ben-Aharon
bc82f9d6a8 fix: dbml export default time bug (#922) 2025-09-17 15:08:44 +03:00
Guy Ben-Aharon
26dc299cd2 fix: dbml export renaming fields bug (#921) 2025-09-17 14:27:53 +03:00
Guy Ben-Aharon
d6ba4a4074 fix: import dbml set pk field unique (#920) 2025-09-17 11:36:29 +03:00
Jonathan Fishner
d09379e8be feat: add area context menu and UI improvements (#918)
* feat: add area context menu and UI improvements

- Add right-click context menu for areas with edit/delete options
- Add pencil icon on hover for diagram name
- Add dynamic input width for diagram name
- Keep existing useClickAway behavior

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-09-16 14:57:38 +03:00
Guy Ben-Aharon
bdc41c0b74 fix: trigger edit table on canvas from context menu (#919) 2025-09-16 10:15:42 +03:00
Jonathan Fishner
d3dbf41894 fix(sqlite): improve parser to handle tables without column types and fix column detection (#914) 2025-09-15 20:50:44 +03:00
Jonathan Fishner
e6783a89cc refactor: remove dedicated DBML import dialog (#907)
* refactor: remove dedicated DBML import dialog and unify import flow through ImportDatabaseDialog

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-09-15 19:24:35 +03:00
Jonathan Fishner
af3638da7a feat(import-db): add DBML syntax to import database dialog (#768)
* feat(editor): add import DBML syntax in import database dialog

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-09-15 19:01:50 +03:00
Guy Ben-Aharon
8954d893bb feat: add quick table mode on canvas (#915)
* fix: add quick table mode on canvas

* fix

* fix

* fix
2025-09-14 20:42:01 +03:00
Guy Ben-Aharon
1a6688e85e alignment (#912) 2025-09-11 12:32:58 +03:00
Guy Ben-Aharon
5e81c1848a fix(dbml): export array fields without quotes (#911) 2025-09-10 22:24:05 +03:00
Guy Ben-Aharon
2bd9ca25b2 fix: update deps vulns (#909) 2025-09-10 16:37:33 +03:00
Guy Ben-Aharon
b016a70691 fix: move auto arrange to toolbar (#904) 2025-09-07 12:02:33 +03:00
Jonathan Fishner
a0fb1ed08b feat: add zoom navigation buttons to canvas filter for tables and areas (#903)
* feat: add zoom navigation buttons to canvas filter for tables and areas

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-09-04 16:18:50 +03:00
Guy Ben-Aharon
ffddcdcc98 fix: export sql + import metadata lib (#902) 2025-09-04 12:10:56 +03:00
Jonathan Fishner
fe9ef275b8 fix: improve SQL default value parsing for PostgreSQL, MySQL, and SQL Server with proper type handling and casting support (#900) 2025-09-04 11:18:02 +03:00
Guy Ben-Aharon
df89f0b6b9 fix: remove general db creation (#901) 2025-09-03 20:57:12 +03:00
Guy Ben-Aharon
534d2858af readonly editor (#899) 2025-09-03 15:59:21 +03:00
Jonathan Fishner
2a64deebb8 fix(sql-import): handle SQL Server DDL with multiple tables, inline foreign keys, and case-insensitive field matching (#897) 2025-09-02 15:15:15 +03:00
Guy Ben-Aharon
e5e1d59327 fix: reset increment and default when change field (#896) 2025-09-01 18:48:00 +03:00
Guy Ben-Aharon
aa290615ca fix(sql-import): support ALTER TABLE ALTER COLUMN TYPE in PostgreSQL importer (#895) 2025-09-01 17:13:42 +03:00
Guy Ben-Aharon
ec6e46fe81 fix: add support for ALTER TABLE ADD COLUMN in PostgreSQL importer (#892) 2025-09-01 11:45:14 +03:00
Guy Ben-Aharon
ac128d67de align filter (#890) 2025-08-31 19:18:43 +03:00
Guy Ben-Aharon
07937a2f51 fix: export dbml issues after upgrade version (#883)
* fix: dbml export

* fix

* fix

* fix

* fix

* fix
2025-08-27 20:44:18 +03:00
Guy Ben-Aharon
d8e0bc7db8 fix: upgrade dbml lib (#880) 2025-08-27 14:42:02 +03:00
Guy Ben-Aharon
1ce265781b chore(main): release 1.15.1 (#878) 2025-08-27 12:53:00 +03:00
Aaron Dewes
60c5675cbf fix(custom-types): Make schema optional (#866)
* fix(custom-types): Make schema optional

The schema is optional in practice for custom types (as seen in the TS types above), and not always included in exports.

* add nullable

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-27 12:48:14 +03:00
Jonathan Fishner
66b086378c fix: handle quoted identifiers with special characters in SQL import/export and DBML generation (#877)
* fix: handle quoted identifiers with special characters in SQL import/export and DBML generation

* add tests and fix build

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-27 12:21:41 +03:00
Guy Ben-Aharon
abd2a6ccbe fix: add actions menu to diagram list + add duplicate diagram (#876) 2025-08-26 17:10:25 +03:00
Guy Ben-Aharon
459c5f1ce3 chore(main): release 1.15.0 (#835) 2025-08-26 15:15:41 +03:00
Guy Ben-Aharon
44be48ff3a fix: improve creating view to table dependency (#874) 2025-08-26 15:10:11 +03:00
Aaron Dewes
ad8e34483f fix(cla): Harden action (#867)
The CLA action does not need contents: write permission. Limit it to read for security.
2025-08-26 13:41:29 +03:00
Jonathan Fishner
215d57979d fix: preserve composite primary key constraint names across import/export workflows (#869)
* fix: composite primary key constraint names across import/export workflows

* fix: enhance PK index management with automatic lifecycle and improved UI

* fix build

* fix

* fix

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-26 12:48:24 +03:00
Guy Ben-Aharon
ec3719ebce fix: merge relationship & dependency sections to ref section (#870)
* fix: merge relationship & dependency sections to ref section

* fix

* fix

* fix
2025-08-25 20:14:32 +03:00
Guy Ben-Aharon
0a5874a69b feat: support create views (#868)
* feat: support create views

* fix

* fix

* fix

* fix

* fix
2025-08-25 16:14:28 +03:00
Guy Ben-Aharon
7e0fdd1595 fix: open filter by default (#863) 2025-08-21 17:55:45 +03:00
Guy Ben-Aharon
2531a7023f fix: move dbml into sections menu (#862) 2025-08-21 14:48:47 +03:00
Guy Ben-Aharon
73daf0df21 fix: area filter logic (#861) 2025-08-20 15:21:48 +03:00
Jonathan Fishner
c77c983989 feat: add auto increment support for fields with database-specific export (#851)
* feat: add auto increment support for fields with database-specific export

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-19 11:39:26 +03:00
Jonathan Fishner
0aaa451479 fix: prevent false change detection in DBML editor by stripping public schema on import (#858)
* fix: prevent false change detection in DBML editor by stripping public schema on import

* fix(dbml): preserve self-referencing relationships and character varying lengths in DBML import/export
2025-08-18 21:39:24 +03:00
Guy Ben-Aharon
b697e26170 fix(canvas): delete table + area together bug (#859) 2025-08-18 20:56:32 +03:00
Jonathan Fishner
04d91c67b1 fix(sql-import): fix SQL Server foreign key parsing for tables without schema prefix (#857) 2025-08-18 19:13:46 +03:00
Guy Ben-Aharon
d0dee84970 fix(filter): filter toggle issues with no schemas dbs (#856) 2025-08-17 12:54:56 +03:00
Guy Ben-Aharon
b4ccfcdcde fix: set default filter only if has more than 1 schemas (#855) 2025-08-14 11:37:24 +03:00
Guy Ben-Aharon
1759b0b9f2 fix: show default schema first (#854) 2025-08-13 21:08:53 +03:00
Guy Ben-Aharon
ab4845c772 fix: initially show filter when filter active (#853) 2025-08-13 20:20:59 +03:00
Jonathan Fishner
0545b41140 fix: DBML export error with multi-line table comments for SQL Server (#852)
* fix: DBML export error with multi-line table comments for SQL Server

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-13 18:15:38 +03:00
Guy Ben-Aharon
4520f8b1f7 update index.html (#850) 2025-08-13 11:34:08 +03:00
Guy Ben-Aharon
712bdf5b95 fix: filter to default schema on load new diagram (#849) 2025-08-12 18:07:19 +03:00
Jonathan Fishner
d7c9536272 fix: reorder with areas (#846) 2025-08-12 16:25:56 +03:00
Guy Ben-Aharon
815a52f192 update index.html (#848) 2025-08-12 14:31:41 +03:00
Guy Ben-Aharon
f1a4298362 fix: remove unnecessary space (#845) 2025-08-12 11:08:37 +03:00
Guy Ben-Aharon
b8f2141bd2 fix(sidebar): add titles to sidebar (#844)
* update shadcn

* menu v1

* menu v2

* resize menu items

* fix

* fix
2025-08-11 17:38:40 +03:00
Guy Ben-Aharon
eaebe34768 fix(menu): clear file menu (#843) 2025-08-11 11:46:33 +03:00
Jonathan Fishner
0d623a86b1 feat(postgres): add support hash index types (#812)
* feat(postgres): add support for hash index type with single column constraint

* some fixes

* some fixes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-10 20:24:34 +03:00
Guy Ben-Aharon
19fd94c6bd fix(area filter): fix dragging tables over filtered areas (#842) 2025-08-10 15:26:47 +03:00
Guy Ben-Aharon
0da3caeeac fix(table colors): switch to default table color (#841) 2025-08-10 14:42:22 +03:00
Guy Ben-Aharon
cb2ba66233 fix(select-box): fix select box issue in dialog (#840) 2025-08-10 14:06:09 +03:00
Guy Ben-Aharon
8a2267281b alignment of node converters (#839) 2025-08-10 13:36:55 +03:00
Guy Ben-Aharon
41ba251377 fix: update filter on adding table (#838) 2025-08-10 11:04:52 +03:00
Jonathan Fishner
e9c5442d9d feat(filter): filter tables by areas (#836)
* feat: auto-hide/show areas based on table visibility in canvas filter

* fix build

* fix

* fix

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-10 10:13:28 +03:00
Guy Ben-Aharon
4f1d3295c0 fix(filters): refactor diagram filters - remove schema filter (#832)
* refactor(filters): refactor diagram filters

* replace old filters

* fix storage

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix
2025-08-07 14:55:35 +03:00
Jonathan Fishner
5936500ca0 fix: for sqlite import - add more types & include type parameters (#834)
* fix: for sqlite import - add more types

* fix: preserve original data types in SQLite export instead of converting to storage classes

* fix: include type parameters (length, precision, scale) in SQLite export
2025-08-07 14:50:55 +03:00
Jonathan Fishner
43fc1d7fc2 feat: include foreign keys inline in SQLite CREATE TABLE statements (#833) 2025-08-07 11:55:15 +03:00
265 changed files with 109893 additions and 6564 deletions

View File

@@ -7,7 +7,7 @@ on:
permissions: permissions:
actions: write actions: write
contents: write # this can be 'read' if the signatures are in remote repository contents: read
pull-requests: write pull-requests: write
statuses: write statuses: write

2
.nvmrc
View File

@@ -1 +1 @@
v22.5.1 v22.18.0

View File

@@ -1,5 +1,122 @@
# Changelog # Changelog
## [1.17.0](https://github.com/chartdb/chartdb/compare/v1.16.0...v1.17.0) (2025-10-27)
### Features
* create relationships on canvas modal ([#946](https://github.com/chartdb/chartdb/issues/946)) ([34475ad](https://github.com/chartdb/chartdb/commit/34475add32f11323589ef092ccf2a8e9152ff272))
### Bug Fixes
* add auto-increment field detection in smart-query import ([#935](https://github.com/chartdb/chartdb/issues/935)) ([57b3b87](https://github.com/chartdb/chartdb/commit/57b3b8777fd0a445abf0ba6603faab612d469d5c))
* add open table in editor from canvas edit ([#952](https://github.com/chartdb/chartdb/issues/952)) ([7d811de](https://github.com/chartdb/chartdb/commit/7d811de097eb11e51012772fa6bf586fd0b16c62))
* add rels export dbml ([#937](https://github.com/chartdb/chartdb/issues/937)) ([c3c646b](https://github.com/chartdb/chartdb/commit/c3c646bf7cbb1328f4b2eb85c9a7e929f0fcd3b9))
* add support for arrays ([#949](https://github.com/chartdb/chartdb/issues/949)) ([49328d8](https://github.com/chartdb/chartdb/commit/49328d8fbd7786f6c0c04cd5605d43a24cbf10ea))
* add support for parsing default values in DBML ([#948](https://github.com/chartdb/chartdb/issues/948)) ([459698b](https://github.com/chartdb/chartdb/commit/459698b5d0a1ff23a3719c2e55e4ab2e2384c4fe))
* add timestampz and int as datatypes to postgres ([#940](https://github.com/chartdb/chartdb/issues/940)) ([b15bc94](https://github.com/chartdb/chartdb/commit/b15bc945acb96d7cb3832b3b1b607dfcaef9e5ca))
* auto-enter edit mode when creating new tables from canvas ([#943](https://github.com/chartdb/chartdb/issues/943)) ([bcd8aa9](https://github.com/chartdb/chartdb/commit/bcd8aa9378aa563f40a2b6802cc503be4c882356))
* dbml diff fields types preview ([#934](https://github.com/chartdb/chartdb/issues/934)) ([bb03309](https://github.com/chartdb/chartdb/commit/bb033091b1f64b888822be1423a80f16f5314f6b))
* exit table edit on area click ([#945](https://github.com/chartdb/chartdb/issues/945)) ([38fedce](https://github.com/chartdb/chartdb/commit/38fedcec0c10ea2b3f0b7fc92ca1f5ac9e540389))
* import array fields ([#961](https://github.com/chartdb/chartdb/issues/961)) ([91e713c](https://github.com/chartdb/chartdb/commit/91e713c30a44f1ba7a767ca7816079610136fcb8))
* manipulate schema directly from the canvas ([#947](https://github.com/chartdb/chartdb/issues/947)) ([7ad0e77](https://github.com/chartdb/chartdb/commit/7ad0e7712de975a23b2a337dc0a4a7fb4b122bd1))
* preserve multi-word types in DBML export/import ([#956](https://github.com/chartdb/chartdb/issues/956)) ([9ed27cf](https://github.com/chartdb/chartdb/commit/9ed27cf30cca1312713e80e525138f0c27154936))
* prevent text input glitch when editing table field names ([#944](https://github.com/chartdb/chartdb/issues/944)) ([498655e](https://github.com/chartdb/chartdb/commit/498655e7b77e57eaf641ba86263ce1ef60b93e16))
* resolve canvas filter tree state issues ([#953](https://github.com/chartdb/chartdb/issues/953)) ([ccb29e0](https://github.com/chartdb/chartdb/commit/ccb29e0a574dfa4cfdf0ebf242a4c4aaa48cc37b))
* resolve dbml increment & nullable attributes issue ([#954](https://github.com/chartdb/chartdb/issues/954)) ([2c4b344](https://github.com/chartdb/chartdb/commit/2c4b344efb24041e7f607fc6124e109b69aaa457))
* show SQL Script option conditionally for databases without DDL support ([#960](https://github.com/chartdb/chartdb/issues/960)) ([acf6d4b](https://github.com/chartdb/chartdb/commit/acf6d4b3654d8868b8a8ebf717c608d9749b71da))
* use flag for custom types ([#951](https://github.com/chartdb/chartdb/issues/951)) ([62dec48](https://github.com/chartdb/chartdb/commit/62dec4857211b705a8039691da1772263ea986fe))
## [1.16.0](https://github.com/chartdb/chartdb/compare/v1.15.1...v1.16.0) (2025-09-24)
### Features
* add area context menu and UI improvements ([#918](https://github.com/chartdb/chartdb/issues/918)) ([d09379e](https://github.com/chartdb/chartdb/commit/d09379e8be0fa3c83ca77ff62ae815fe4db9869b))
* add quick table mode on canvas ([#915](https://github.com/chartdb/chartdb/issues/915)) ([8954d89](https://github.com/chartdb/chartdb/commit/8954d893bbfee45bb311380115fb14ebbf3a3133))
* add zoom navigation buttons to canvas filter for tables and areas ([#903](https://github.com/chartdb/chartdb/issues/903)) ([a0fb1ed](https://github.com/chartdb/chartdb/commit/a0fb1ed08ba18b66354fa3498d610097a83d4afc))
* **import-db:** add DBML syntax to import database dialog ([#768](https://github.com/chartdb/chartdb/issues/768)) ([af3638d](https://github.com/chartdb/chartdb/commit/af3638da7a9b70f281ceaddbc2f712a713d90cda))
### Bug Fixes
* add areas width and height + table width to diff check ([#931](https://github.com/chartdb/chartdb/issues/931)) ([98f6edd](https://github.com/chartdb/chartdb/commit/98f6edd5c8a8e9130e892b2d841744e0cf63a7bf))
* add diff x,y ([#928](https://github.com/chartdb/chartdb/issues/928)) ([e4c4a3b](https://github.com/chartdb/chartdb/commit/e4c4a3b35484d9ece955a5aec577603dde73d634))
* add support for ALTER TABLE ADD COLUMN in PostgreSQL importer ([#892](https://github.com/chartdb/chartdb/issues/892)) ([ec6e46f](https://github.com/chartdb/chartdb/commit/ec6e46fe81ea1806c179c50a4c5779d8596008aa))
* add tests for diff ([#930](https://github.com/chartdb/chartdb/issues/930)) ([47a7a73](https://github.com/chartdb/chartdb/commit/47a7a73a137b87dfa6e67aff5f939cf64ccf4601))
* dbml edit mode glitch ([#925](https://github.com/chartdb/chartdb/issues/925)) ([93d72a8](https://github.com/chartdb/chartdb/commit/93d72a896bab9aa79d8ea2f876126887e432214c))
* dbml export default time bug ([#922](https://github.com/chartdb/chartdb/issues/922)) ([bc82f9d](https://github.com/chartdb/chartdb/commit/bc82f9d6a8fe4de2f7e0fc465e0a20c5dbf8f41d))
* dbml export renaming fields bug ([#921](https://github.com/chartdb/chartdb/issues/921)) ([26dc299](https://github.com/chartdb/chartdb/commit/26dc299cd28e9890d191c13f84a15ac38ae48b11))
* **dbml:** export array fields without quotes ([#911](https://github.com/chartdb/chartdb/issues/911)) ([5e81c18](https://github.com/chartdb/chartdb/commit/5e81c1848aaa911990e1e881d62525f5254d6d34))
* diff logic ([#927](https://github.com/chartdb/chartdb/issues/927)) ([1b8d51b](https://github.com/chartdb/chartdb/commit/1b8d51b73c4ed4b7c5929adcb17a44927c7defca))
* export dbml issues after upgrade version ([#883](https://github.com/chartdb/chartdb/issues/883)) ([07937a2](https://github.com/chartdb/chartdb/commit/07937a2f51708b1c10b45c2bd1f9a9acf5c3f708))
* export sql + import metadata lib ([#902](https://github.com/chartdb/chartdb/issues/902)) ([ffddcdc](https://github.com/chartdb/chartdb/commit/ffddcdcc987bacb0e0d7e8dea27d08d3a8c5a8c8))
* handle bidirectional relationships in DBML export ([#924](https://github.com/chartdb/chartdb/issues/924)) ([9991077](https://github.com/chartdb/chartdb/commit/99910779789a9c6ef113d06bc3de31e35b9b04d1))
* import dbml set pk field unique ([#920](https://github.com/chartdb/chartdb/issues/920)) ([d6ba4a4](https://github.com/chartdb/chartdb/commit/d6ba4a40749d85d2703f120600df4345dab3c561))
* improve SQL default value parsing for PostgreSQL, MySQL, and SQL Server with proper type handling and casting support ([#900](https://github.com/chartdb/chartdb/issues/900)) ([fe9ef27](https://github.com/chartdb/chartdb/commit/fe9ef275b8619dcfd7e57541a62a6237a16d29a8))
* move area utils ([#932](https://github.com/chartdb/chartdb/issues/932)) ([2dc1a6f](https://github.com/chartdb/chartdb/commit/2dc1a6fc7519e0a455b0e1306601195deb156c96))
* move auto arrange to toolbar ([#904](https://github.com/chartdb/chartdb/issues/904)) ([b016a70](https://github.com/chartdb/chartdb/commit/b016a70691bc22af5720b4de683e8c9353994fcc))
* remove general db creation ([#901](https://github.com/chartdb/chartdb/issues/901)) ([df89f0b](https://github.com/chartdb/chartdb/commit/df89f0b6b9ba3fcc8b05bae4f60c0dc4ad1d2215))
* remove many to many rel option ([#933](https://github.com/chartdb/chartdb/issues/933)) ([c567c0a](https://github.com/chartdb/chartdb/commit/c567c0a5f39157b2c430e92192b6750304d7a834))
* reset increment and default when change field ([#896](https://github.com/chartdb/chartdb/issues/896)) ([e5e1d59](https://github.com/chartdb/chartdb/commit/e5e1d5932762422ea63acfd6cf9fe4f03aa822f7))
* **sql-import:** handle SQL Server DDL with multiple tables, inline foreign keys, and case-insensitive field matching ([#897](https://github.com/chartdb/chartdb/issues/897)) ([2a64dee](https://github.com/chartdb/chartdb/commit/2a64deebb87a11ee3892024c3273d682bb86f7ef))
* **sql-import:** support ALTER TABLE ALTER COLUMN TYPE in PostgreSQL importer ([#895](https://github.com/chartdb/chartdb/issues/895)) ([aa29061](https://github.com/chartdb/chartdb/commit/aa290615caf806d7d0374c848d50b4636fde7e96))
* **sqlite:** improve parser to handle tables without column types and fix column detection ([#914](https://github.com/chartdb/chartdb/issues/914)) ([d3dbf41](https://github.com/chartdb/chartdb/commit/d3dbf41894d74f0ffce9afe3bd810f065aa53017))
* trigger edit table on canvas from context menu ([#919](https://github.com/chartdb/chartdb/issues/919)) ([bdc41c0](https://github.com/chartdb/chartdb/commit/bdc41c0b74d9d9918e7b6cd2152fa07c0c58ce60))
* update deps vulns ([#909](https://github.com/chartdb/chartdb/issues/909)) ([2bd9ca2](https://github.com/chartdb/chartdb/commit/2bd9ca25b2c7b1f053ff4fdc8c5cfc1b0e65901d))
* upgrade dbml lib ([#880](https://github.com/chartdb/chartdb/issues/880)) ([d8e0bc7](https://github.com/chartdb/chartdb/commit/d8e0bc7db8881971ddaea7177bcebee13cc865f6))
## [1.15.1](https://github.com/chartdb/chartdb/compare/v1.15.0...v1.15.1) (2025-08-27)
### Bug Fixes
* add actions menu to diagram list + add duplicate diagram ([#876](https://github.com/chartdb/chartdb/issues/876)) ([abd2a6c](https://github.com/chartdb/chartdb/commit/abd2a6ccbe1aa63db44ec28b3eff525cc5d3f8b0))
* **custom-types:** Make schema optional ([#866](https://github.com/chartdb/chartdb/issues/866)) ([60c5675](https://github.com/chartdb/chartdb/commit/60c5675cbfe205859d2d0c9848d8345a0a854671))
* handle quoted identifiers with special characters in SQL import/export and DBML generation ([#877](https://github.com/chartdb/chartdb/issues/877)) ([66b0863](https://github.com/chartdb/chartdb/commit/66b086378cd63347acab5fc7f13db7db4feaa872))
## [1.15.0](https://github.com/chartdb/chartdb/compare/v1.14.0...v1.15.0) (2025-08-26)
### Features
* add auto increment support for fields with database-specific export ([#851](https://github.com/chartdb/chartdb/issues/851)) ([c77c983](https://github.com/chartdb/chartdb/commit/c77c983989ae38a6b1139dd9015f4f3178d4e103))
* **filter:** filter tables by areas ([#836](https://github.com/chartdb/chartdb/issues/836)) ([e9c5442](https://github.com/chartdb/chartdb/commit/e9c5442d9df2beadad78187da3363bb6406636c4))
* include foreign keys inline in SQLite CREATE TABLE statements ([#833](https://github.com/chartdb/chartdb/issues/833)) ([43fc1d7](https://github.com/chartdb/chartdb/commit/43fc1d7fc26876b22c61405f6c3df89fc66b7992))
* **postgres:** add support hash index types ([#812](https://github.com/chartdb/chartdb/issues/812)) ([0d623a8](https://github.com/chartdb/chartdb/commit/0d623a86b1cb7cbd223e10ad23d09fc0e106c006))
* support create views ([#868](https://github.com/chartdb/chartdb/issues/868)) ([0a5874a](https://github.com/chartdb/chartdb/commit/0a5874a69b6323145430c1fb4e3482ac7da4916c))
### Bug Fixes
* area filter logic ([#861](https://github.com/chartdb/chartdb/issues/861)) ([73daf0d](https://github.com/chartdb/chartdb/commit/73daf0df2142a29c2eeebe60b43198bcca869026))
* **area filter:** fix dragging tables over filtered areas ([#842](https://github.com/chartdb/chartdb/issues/842)) ([19fd94c](https://github.com/chartdb/chartdb/commit/19fd94c6bde3a9ec749cd1ccacbedb6abc96d037))
* **canvas:** delete table + area together bug ([#859](https://github.com/chartdb/chartdb/issues/859)) ([b697e26](https://github.com/chartdb/chartdb/commit/b697e26170da95dcb427ff6907b6f663c98ba59f))
* **cla:** Harden action ([#867](https://github.com/chartdb/chartdb/issues/867)) ([ad8e344](https://github.com/chartdb/chartdb/commit/ad8e34483fdf4226de76c9e7768bc2ba9bf154de))
* DBML export error with multi-line table comments for SQL Server ([#852](https://github.com/chartdb/chartdb/issues/852)) ([0545b41](https://github.com/chartdb/chartdb/commit/0545b411407b2449220d10981a04c3e368a90ca3))
* filter to default schema on load new diagram ([#849](https://github.com/chartdb/chartdb/issues/849)) ([712bdf5](https://github.com/chartdb/chartdb/commit/712bdf5b958919d940c4f2a1c3b7c7e969990f02))
* **filter:** filter toggle issues with no schemas dbs ([#856](https://github.com/chartdb/chartdb/issues/856)) ([d0dee84](https://github.com/chartdb/chartdb/commit/d0dee849702161d979b4f589a7e6579fbaade22d))
* **filters:** refactor diagram filters - remove schema filter ([#832](https://github.com/chartdb/chartdb/issues/832)) ([4f1d329](https://github.com/chartdb/chartdb/commit/4f1d3295c09782ab46d82ce21b662032aa094f22))
* for sqlite import - add more types & include type parameters ([#834](https://github.com/chartdb/chartdb/issues/834)) ([5936500](https://github.com/chartdb/chartdb/commit/5936500ca00a57b3f161616264c26152a13c36d2))
* improve creating view to table dependency ([#874](https://github.com/chartdb/chartdb/issues/874)) ([44be48f](https://github.com/chartdb/chartdb/commit/44be48ff3ad1361279331c17364090b13af471a1))
* initially show filter when filter active ([#853](https://github.com/chartdb/chartdb/issues/853)) ([ab4845c](https://github.com/chartdb/chartdb/commit/ab4845c7728e6e0b2d852f8005921fd90630eef9))
* **menu:** clear file menu ([#843](https://github.com/chartdb/chartdb/issues/843)) ([eaebe34](https://github.com/chartdb/chartdb/commit/eaebe3476824af779214a354b3e991923a22f195))
* merge relationship & dependency sections to ref section ([#870](https://github.com/chartdb/chartdb/issues/870)) ([ec3719e](https://github.com/chartdb/chartdb/commit/ec3719ebce4664b2aa6e3322fb3337e72bc21015))
* move dbml into sections menu ([#862](https://github.com/chartdb/chartdb/issues/862)) ([2531a70](https://github.com/chartdb/chartdb/commit/2531a7023f36ef29e67c0da6bca4fd0346b18a51))
* open filter by default ([#863](https://github.com/chartdb/chartdb/issues/863)) ([7e0fdd1](https://github.com/chartdb/chartdb/commit/7e0fdd1595bffe29e769d29602d04f42edfe417e))
* preserve composite primary key constraint names across import/export workflows ([#869](https://github.com/chartdb/chartdb/issues/869)) ([215d579](https://github.com/chartdb/chartdb/commit/215d57979df2e91fa61988acff590daad2f4e771))
* prevent false change detection in DBML editor by stripping public schema on import ([#858](https://github.com/chartdb/chartdb/issues/858)) ([0aaa451](https://github.com/chartdb/chartdb/commit/0aaa451479911d047e4cc83f063afa68a122ba9b))
* remove unnecessary space ([#845](https://github.com/chartdb/chartdb/issues/845)) ([f1a4298](https://github.com/chartdb/chartdb/commit/f1a429836221aacdda73b91665bf33ffb011164c))
* reorder with areas ([#846](https://github.com/chartdb/chartdb/issues/846)) ([d7c9536](https://github.com/chartdb/chartdb/commit/d7c9536272cf1d42104b7064ea448d128d091a20))
* **select-box:** fix select box issue in dialog ([#840](https://github.com/chartdb/chartdb/issues/840)) ([cb2ba66](https://github.com/chartdb/chartdb/commit/cb2ba66233c8c04e2d963cf2d210499d8512a268))
* set default filter only if has more than 1 schemas ([#855](https://github.com/chartdb/chartdb/issues/855)) ([b4ccfcd](https://github.com/chartdb/chartdb/commit/b4ccfcdcde2f3565b0d3bbc46fa1715feb6cd925))
* show default schema first ([#854](https://github.com/chartdb/chartdb/issues/854)) ([1759b0b](https://github.com/chartdb/chartdb/commit/1759b0b9f271ed25f7c71f26c344e3f1d97bc5fb))
* **sidebar:** add titles to sidebar ([#844](https://github.com/chartdb/chartdb/issues/844)) ([b8f2141](https://github.com/chartdb/chartdb/commit/b8f2141bd2e67272030896fb4009a7925f9f09e4))
* **sql-import:** fix SQL Server foreign key parsing for tables without schema prefix ([#857](https://github.com/chartdb/chartdb/issues/857)) ([04d91c6](https://github.com/chartdb/chartdb/commit/04d91c67b1075e94948f75186878e633df7abbca))
* **table colors:** switch to default table color ([#841](https://github.com/chartdb/chartdb/issues/841)) ([0da3cae](https://github.com/chartdb/chartdb/commit/0da3caeeac37926dd22f38d98423611f39c0412a))
* update filter on adding table ([#838](https://github.com/chartdb/chartdb/issues/838)) ([41ba251](https://github.com/chartdb/chartdb/commit/41ba25137789dda25266178cd7c96ecbb37e62a4))
## [1.14.0](https://github.com/chartdb/chartdb/compare/v1.13.2...v1.14.0) (2025-08-04) ## [1.14.0](https://github.com/chartdb/chartdb/compare/v1.13.2...v1.14.0) (2025-08-04)

View File

@@ -4,8 +4,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="max-image-preview:large" /> <meta name="robots" content="noindex, max-image-preview:large" />
<title>ChartDB - Create & Visualize Database Schema Diagrams</title> <title>ChartDB - Create & Visualize Database Schema Diagrams</title>
<link rel="canonical" href="https://chartdb.io" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
@@ -15,14 +16,19 @@
<script src="/config.js"></script> <script src="/config.js"></script>
<script> <script>
// Load analytics only if not disabled // Load analytics only if not disabled
(function() { (function () {
const disableAnalytics = (window.env && window.env.DISABLE_ANALYTICS === 'true') || const disableAnalytics =
(typeof process !== 'undefined' && process.env && process.env.VITE_DISABLE_ANALYTICS === 'true'); (window.env && window.env.DISABLE_ANALYTICS === 'true') ||
(typeof process !== 'undefined' &&
process.env &&
process.env.VITE_DISABLE_ANALYTICS === 'true');
if (!disableAnalytics) { if (!disableAnalytics) {
const script = document.createElement('script'); const script = document.createElement('script');
script.src = 'https://cdn.usefathom.com/script.js'; script.src = 'https://cdn.usefathom.com/script.js';
script.setAttribute('data-site', 'PRHIVBNN'); script.setAttribute('data-site', 'PRHIVBNN');
script.setAttribute('data-canonical', 'false');
script.setAttribute('data-spa', 'auto');
script.defer = true; script.defer = true;
document.head.appendChild(script); document.head.appendChild(script);
} }

2511
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "chartdb", "name": "chartdb",
"private": true, "private": true,
"version": "1.14.0", "version": "1.17.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -17,7 +17,7 @@
}, },
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^0.0.51", "@ai-sdk/openai": "^0.0.51",
"@dbml/core": "^3.9.5", "@dbml/core": "^3.13.9",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
@@ -26,22 +26,22 @@
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1", "@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "1.2.0", "@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-select": "^2.1.1", "@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.2.7",
"@uidotdev/usehooks": "^2.4.1", "@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.8.2", "@xyflow/react": "^12.8.2",
"ahooks": "^3.8.1", "ahooks": "^3.8.1",
@@ -64,10 +64,13 @@
"react-helmet-async": "^2.0.5", "react-helmet-async": "^2.0.5",
"react-hotkeys-hook": "^4.5.0", "react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1", "react-i18next": "^15.0.1",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^2.0.22", "react-resizable-panels": "^2.0.22",
"react-responsive": "^10.0.0", "react-responsive": "^10.0.0",
"react-router-dom": "^7.1.1", "react-router-dom": "^7.1.1",
"react-use": "^17.5.1", "react-use": "^17.5.1",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"tailwind-merge": "^2.4.0", "tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.6", "timeago-react": "^3.0.6",

View File

@@ -1,4 +1,4 @@
User-agent: * User-agent: *
Allow: / Disallow: /
Sitemap: https://app.chartdb.io/sitemap.xml Sitemap: https://app.chartdb.io/sitemap.xml

View File

@@ -1,7 +1,7 @@
import { cva } from 'class-variance-authority'; import { cva } from 'class-variance-authority';
export const buttonVariants = cva( export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -0,0 +1,137 @@
import React from 'react';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { Slot } from '@radix-ui/react-slot';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { buttonVariants } from './button-variants';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/dropdown-menu/dropdown-menu';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/tooltip/tooltip';
export interface ButtonAlternative {
label: string;
onClick: () => void;
disabled?: boolean;
icon?: React.ReactNode;
className?: string;
tooltip?: string;
}
export interface ButtonWithAlternativesProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
alternatives: Array<ButtonAlternative>;
dropdownTriggerClassName?: string;
chevronDownIconClassName?: string;
}
const ButtonWithAlternatives = React.forwardRef<
HTMLButtonElement,
ButtonWithAlternativesProps
>(
(
{
className,
variant,
size,
asChild = false,
alternatives,
children,
onClick,
dropdownTriggerClassName,
chevronDownIconClassName,
...props
},
ref
) => {
const Comp = asChild ? Slot : 'button';
const hasAlternatives = (alternatives?.length ?? 0) > 0;
return (
<div className="inline-flex items-stretch">
<Comp
className={cn(
buttonVariants({ variant, size }),
{ 'rounded-r-none': hasAlternatives },
className
)}
ref={ref}
onClick={onClick}
{...props}
>
{children}
</Comp>
{hasAlternatives ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={cn(
buttonVariants({ variant, size }),
'rounded-l-none border-l border-l-primary/5 px-2 min-w-0',
className?.includes('h-') &&
className.match(/h-\d+/)?.[0],
className?.includes('text-') &&
className.match(/text-\w+/)?.[0],
dropdownTriggerClassName
)}
type="button"
>
<ChevronDownIcon
className={cn(
'size-4 shrink-0',
chevronDownIconClassName
)}
/>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{alternatives.map((alternative, index) => {
const menuItem = (
<DropdownMenuItem
key={index}
onClick={alternative.onClick}
disabled={alternative.disabled}
className={cn(alternative.className)}
>
<span className="flex w-full items-center justify-between gap-2">
{alternative.label}
{alternative.icon}
</span>
</DropdownMenuItem>
);
if (alternative.tooltip) {
return (
<Tooltip key={index}>
<TooltipTrigger asChild>
{menuItem}
</TooltipTrigger>
<TooltipContent side="left">
{alternative.tooltip}
</TooltipContent>
</Tooltip>
);
}
return menuItem;
})}
</DropdownMenuContent>
</DropdownMenu>
) : null}
</div>
);
}
);
ButtonWithAlternatives.displayName = 'ButtonWithAlternatives';
export { ButtonWithAlternatives };

View File

@@ -38,7 +38,7 @@ export interface CodeSnippetProps {
className?: string; className?: string;
code: string; code: string;
codeToCopy?: string; codeToCopy?: string;
language?: 'sql' | 'shell'; language?: 'sql' | 'shell' | 'dbml';
loading?: boolean; loading?: boolean;
autoScroll?: boolean; autoScroll?: boolean;
isComplete?: boolean; isComplete?: boolean;

View File

@@ -9,12 +9,14 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
base: 'vs-dark', base: 'vs-dark',
inherit: true, inherit: true,
rules: [ rules: [
{ token: 'comment', foreground: '6A9955' }, // Comments
{ token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords { token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords
{ token: 'string', foreground: 'CE9178' }, // Strings { token: 'string', foreground: 'CE9178' }, // Strings
{ token: 'annotation', foreground: '9CDCFE' }, // [annotations] { token: 'annotation', foreground: '9CDCFE' }, // [annotations]
{ token: 'delimiter', foreground: 'D4D4D4' }, // Braces {} { token: 'delimiter', foreground: 'D4D4D4' }, // Braces {}
{ token: 'operator', foreground: 'D4D4D4' }, // Operators { token: 'operator', foreground: 'D4D4D4' }, // Operators
{ token: 'datatype', foreground: '4EC9B0' }, // Data types { token: 'type', foreground: '4EC9B0' }, // Data types
{ token: 'identifier', foreground: '9CDCFE' }, // Field names
], ],
colors: {}, colors: {},
}); });
@@ -23,12 +25,14 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
base: 'vs', base: 'vs',
inherit: true, inherit: true,
rules: [ rules: [
{ token: 'comment', foreground: '008000' }, // Comments
{ token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords { token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords
{ token: 'string', foreground: 'A31515' }, // Strings { token: 'string', foreground: 'A31515' }, // Strings
{ token: 'annotation', foreground: '001080' }, // [annotations] { token: 'annotation', foreground: '001080' }, // [annotations]
{ token: 'delimiter', foreground: '000000' }, // Braces {} { token: 'delimiter', foreground: '000000' }, // Braces {}
{ token: 'operator', foreground: '000000' }, // Operators { token: 'operator', foreground: '000000' }, // Operators
{ token: 'type', foreground: '267F99' }, // Data types { token: 'type', foreground: '267F99' }, // Data types
{ token: 'identifier', foreground: '001080' }, // Field names
], ],
colors: {}, colors: {},
}); });
@@ -37,23 +41,59 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
const datatypePattern = dataTypesNames.join('|'); const datatypePattern = dataTypesNames.join('|');
monaco.languages.setMonarchTokensProvider('dbml', { monaco.languages.setMonarchTokensProvider('dbml', {
keywords: ['Table', 'Ref', 'Indexes', 'Note', 'Enum'], keywords: ['Table', 'Ref', 'Indexes', 'Note', 'Enum', 'enum'],
datatypes: dataTypesNames, datatypes: dataTypesNames,
operators: ['>', '<', '-'],
tokenizer: { tokenizer: {
root: [ root: [
// Comments
[/\/\/.*$/, 'comment'],
// Keywords - case insensitive
[ [
/\b([Tt][Aa][Bb][Ll][Ee]|[Ee][Nn][Uu][Mm]|[Rr][Ee][Ff]|[Ii][Nn][Dd][Ee][Xx][Ee][Ss]|[Nn][Oo][Tt][Ee])\b/, /\b([Tt][Aa][Bb][Ll][Ee]|[Ee][Nn][Uu][Mm]|[Rr][Ee][Ff]|[Ii][Nn][Dd][Ee][Xx][Ee][Ss]|[Nn][Oo][Tt][Ee])\b/,
'keyword', 'keyword',
], ],
// Annotations in brackets
[/\[.*?\]/, 'annotation'], [/\[.*?\]/, 'annotation'],
// Strings
[/'''/, 'string', '@tripleQuoteString'], [/'''/, 'string', '@tripleQuoteString'],
[/".*?"/, 'string'], [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-terminated string
[/'.*?'/, 'string'], [/'([^'\\]|\\.)*$/, 'string.invalid'], // non-terminated string
[/"/, 'string', '@string_double'],
[/'/, 'string', '@string_single'],
[/`.*?`/, 'string'], [/`.*?`/, 'string'],
[/[{}]/, 'delimiter'],
[/[<>]/, 'operator'], // Delimiters and operators
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching [/[{}()]/, 'delimiter'],
[/[<>-]/, 'operator'],
[/:/, 'delimiter'],
// Data types
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'],
// Numbers
[/\d+/, 'number'],
// Identifiers
[/[a-zA-Z_]\w*/, 'identifier'],
], ],
string_double: [
[/[^\\"]+/, 'string'],
[/\\./, 'string.escape'],
[/"/, 'string', '@pop'],
],
string_single: [
[/[^\\']+/, 'string'],
[/\\./, 'string.escape'],
[/'/, 'string', '@pop'],
],
tripleQuoteString: [ tripleQuoteString: [
[/[^']+/, 'string'], [/[^']+/, 'string'],
[/'''/, 'string', '@pop'], [/'''/, 'string', '@pop'],

View File

@@ -5,27 +5,45 @@ import {
PopoverTrigger, PopoverTrigger,
} from '@/components/popover/popover'; } from '@/components/popover/popover';
import { colorOptions } from '@/lib/colors'; import { colorOptions } from '@/lib/colors';
import { cn } from '@/lib/utils';
export interface ColorPickerProps { export interface ColorPickerProps {
color: string; color: string;
onChange: (color: string) => void; onChange: (color: string) => void;
disabled?: boolean;
popoverOnMouseDown?: (e: React.MouseEvent) => void;
popoverOnClick?: (e: React.MouseEvent) => void;
} }
export const ColorPicker = React.forwardRef< export const ColorPicker = React.forwardRef<
React.ElementRef<typeof PopoverTrigger>, React.ElementRef<typeof PopoverTrigger>,
ColorPickerProps ColorPickerProps
>(({ color, onChange }, ref) => { >(({ color, onChange, disabled, popoverOnMouseDown, popoverOnClick }, ref) => {
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild ref={ref}> <PopoverTrigger
asChild
ref={ref}
disabled={disabled}
{...(disabled ? { onClick: (e) => e.preventDefault() } : {})}
>
<div <div
className="h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md" className={cn(
'h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md',
{
'hover:shadow-none cursor-default': disabled,
}
)}
style={{ style={{
backgroundColor: color, backgroundColor: color,
}} }}
/> />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-fit"> <PopoverContent
className="w-fit"
onMouseDown={popoverOnMouseDown}
onClick={popoverOnClick}
>
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{colorOptions.map((option) => ( {colorOptions.map((option) => (
<div <div

View File

@@ -4,6 +4,7 @@ import { Cross2Icon } from '@radix-ui/react-icons';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ScrollArea } from '../scroll-area/scroll-area'; import { ScrollArea } from '../scroll-area/scroll-area';
import { ChevronLeft } from 'lucide-react';
const Dialog = DialogPrimitive.Root; const Dialog = DialogPrimitive.Root;
@@ -32,28 +33,75 @@ const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
showClose?: boolean; showClose?: boolean;
showBack?: boolean;
backButtonClassName?: string;
blurBackground?: boolean;
forceOverlay?: boolean;
onBackClick?: () => void;
} }
>(({ className, children, showClose, ...props }, ref) => ( >(
<DialogPortal> (
<DialogOverlay /> {
<DialogPrimitive.Content className,
ref={ref} children,
className={cn( showClose,
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', showBack,
className onBackClick,
)} backButtonClassName,
{...props} blurBackground,
> forceOverlay,
{children} ...props
{showClose && ( },
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> ref
<Cross2Icon className="size-4" /> ) => (
<span className="sr-only">Close</span> <DialogPortal>
</DialogPrimitive.Close> {forceOverlay ? (
)} <div
</DialogPrimitive.Content> className={cn(
</DialogPortal> 'fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
)); {
'bg-black/80': !blurBackground,
'bg-black/30 backdrop-blur-sm': blurBackground,
}
)}
data-state="open"
/>
) : null}
<DialogOverlay
className={cn({
'bg-black/30 backdrop-blur-sm': blurBackground,
})}
/>
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
>
{children}
{showBack && (
<button
onClick={() => onBackClick?.()}
className={cn(
'absolute left-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground',
backButtonClassName
)}
>
<ChevronLeft className="size-4" />
</button>
)}
{showClose && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="size-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
);
DialogContent.displayName = DialogPrimitive.Content.displayName; DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({

View File

@@ -1,9 +1,16 @@
import React, { forwardRef } from 'react'; import React, { forwardRef } from 'react';
import EmptyStateImage from '@/assets/empty_state.png'; import EmptyStateImage from '@/assets/empty_state.png';
import EmptyStateImageDark from '@/assets/empty_state_dark.png'; import EmptyStateImageDark from '@/assets/empty_state_dark.png';
import { Label } from '@/components/label/label';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useTheme } from '@/hooks/use-theme'; import { useTheme } from '@/hooks/use-theme';
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from '../empty/empty';
export interface EmptyStateProps { export interface EmptyStateProps {
title: string; title: string;
@@ -38,26 +45,29 @@ export const EmptyState = forwardRef<
className className
)} )}
> >
<img <Empty>
src={ <EmptyHeader>
effectiveTheme === 'dark' <EmptyMedia variant="icon">
? EmptyStateImageDark {/* <Group /> */}
: EmptyStateImage <img
} src={
alt="Empty state" effectiveTheme === 'dark'
className={cn('mb-2 w-20', imageClassName)} ? EmptyStateImageDark
/> : EmptyStateImage
<Label className={cn('text-base', titleClassName)}> }
{title} alt="Empty state"
</Label> className={cn('p-2', imageClassName)}
<Label />
className={cn( </EmptyMedia>
'text-sm text-center font-normal text-muted-foreground', <EmptyTitle className={titleClassName}>
descriptionClassName {title}
)} </EmptyTitle>
> <EmptyDescription className={descriptionClassName}>
{description} {description}
</Label> </EmptyDescription>
</EmptyHeader>
<EmptyContent />
</Empty>
</div> </div>
); );
} }

View File

@@ -0,0 +1,105 @@
import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils/index';
function Empty({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty"
className={cn(
'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12',
className
)}
{...props}
/>
);
}
function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty-header"
className={cn(
'flex max-w-sm flex-col items-center gap-2 text-center',
className
)}
{...props}
/>
);
}
const emptyMediaVariants = cva(
'mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-transparent',
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
},
},
defaultVariants: {
variant: 'default',
},
}
);
function EmptyMedia({
className,
variant = 'default',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {
return (
<div
data-slot="empty-icon"
data-variant={variant}
className={cn(emptyMediaVariants({ variant, className }))}
{...props}
/>
);
}
function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty-title"
className={cn('text-lg font-medium tracking-tight', className)}
{...props}
/>
);
}
function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
<div
data-slot="empty-description"
className={cn(
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
className
)}
{...props}
/>
);
}
function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty-content"
className={cn(
'flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm',
className
)}
{...props}
/>
);
}
export {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
};

View File

@@ -2,16 +2,13 @@ import React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
export interface InputProps const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => { ({ className, type, ...props }, ref) => {
return ( return (
<input <input
type={type} type={type}
className={cn( className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className className
)} )}
ref={ref} ref={ref}

View File

@@ -27,6 +27,7 @@ export interface SelectBoxOption {
regex?: string; regex?: string;
extractRegex?: RegExp; extractRegex?: RegExp;
group?: string; group?: string;
icon?: React.ReactNode;
} }
export interface SelectBoxProps { export interface SelectBoxProps {
@@ -53,6 +54,11 @@ export interface SelectBoxProps {
open?: boolean; open?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
popoverClassName?: string; popoverClassName?: string;
readonly?: boolean;
footerButtons?: React.ReactNode;
commandOnMouseDown?: (e: React.MouseEvent) => void;
commandOnClick?: (e: React.MouseEvent) => void;
onSearchChange?: (search: string) => void;
} }
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>( export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
@@ -78,6 +84,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
open, open,
onOpenChange: setOpen, onOpenChange: setOpen,
popoverClassName, popoverClassName,
readonly,
footerButtons,
commandOnMouseDown,
commandOnClick,
onSearchChange,
}, },
ref ref
) => { ) => {
@@ -94,6 +105,10 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
setOpen?.(isOpen); setOpen?.(isOpen);
setIsOpen(isOpen); setIsOpen(isOpen);
if (isOpen) {
setSearchTerm('');
}
setTimeout(() => (document.body.style.pointerEvents = ''), 500); setTimeout(() => (document.body.style.pointerEvents = ''), 500);
}, },
[setOpen] [setOpen]
@@ -148,18 +163,20 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`} className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`}
> >
<span>{option.label}</span> <span>{option.label}</span>
<span {!readonly ? (
onClick={(e) => { <span
e.preventDefault(); onClick={(e) => {
handleSelect(option.value); e.preventDefault();
}} handleSelect(option.value);
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground" }}
> className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
<Cross2Icon /> >
</span> <Cross2Icon />
</span>
) : null}
</span> </span>
)), )),
[options, value, handleSelect, oneLine, keepOrder] [options, value, handleSelect, oneLine, keepOrder, readonly]
); );
const isAllSelected = React.useMemo( const isAllSelected = React.useMemo(
@@ -225,6 +242,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
<CommandItem <CommandItem
className="flex items-center" className="flex items-center"
key={option.value} key={option.value}
value={option.label}
keywords={option.regex ? [option.regex] : undefined} keywords={option.regex ? [option.regex] : undefined}
onSelect={() => onSelect={() =>
handleSelect( handleSelect(
@@ -232,6 +250,8 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
matches?.map((match) => match?.toString()) matches?.map((match) => match?.toString())
) )
} }
onMouseDown={commandOnMouseDown}
onClick={commandOnClick}
> >
{multiple && ( {multiple && (
<div <div
@@ -246,6 +266,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</div> </div>
)} )}
<div className="flex flex-1 items-center truncate"> <div className="flex flex-1 items-center truncate">
{option.icon ? (
<span className="mr-2 shrink-0">
{option.icon}
</span>
) : null}
<span> <span>
{isRegexMatch ? searchTerm : option.label} {isRegexMatch ? searchTerm : option.label}
{!isRegexMatch && optionSuffix {!isRegexMatch && optionSuffix
@@ -272,7 +297,15 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</CommandItem> </CommandItem>
); );
}, },
[value, multiple, searchTerm, handleSelect, optionSuffix] [
value,
multiple,
searchTerm,
handleSelect,
optionSuffix,
commandOnClick,
commandOnMouseDown,
]
); );
return ( return (
@@ -280,7 +313,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}> <PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
<div <div
className={cn( className={cn(
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`, `flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''} ${readonly ? 'pointer-events-none' : ''}`,
className className
)} )}
> >
@@ -350,6 +383,8 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
popoverClassName popoverClassName
)} )}
align="center" align="center"
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
> >
<Command <Command
filter={(value, search, keywords) => { filter={(value, search, keywords) => {
@@ -372,7 +407,10 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
<div className="relative"> <div className="relative">
<CommandInput <CommandInput
value={searchTerm} value={searchTerm}
onValueChange={(e) => setSearchTerm(e)} onValueChange={(e) => {
setSearchTerm(e);
onSearchChange?.(e);
}}
ref={ref} ref={ref}
placeholder={inputPlaceholder ?? 'Search...'} placeholder={inputPlaceholder ?? 'Search...'}
className="h-9" className="h-9"
@@ -439,6 +477,9 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</div> </div>
</ScrollArea> </ScrollArea>
</Command> </Command>
{footerButtons ? (
<div className="border-t">{footerButtons}</div>
) : null}
</PopoverContent> </PopoverContent>
</Popover> </Popover>
); );

View File

@@ -29,6 +29,7 @@ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = '16rem'; const SIDEBAR_WIDTH = '16rem';
const SIDEBAR_WIDTH_MOBILE = '18rem'; const SIDEBAR_WIDTH_MOBILE = '18rem';
const SIDEBAR_WIDTH_ICON = '3rem'; const SIDEBAR_WIDTH_ICON = '3rem';
const SIDEBAR_WIDTH_ICON_EXTENDED = '4rem';
const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
type SidebarContext = { type SidebarContext = {
@@ -142,6 +143,8 @@ const SidebarProvider = React.forwardRef<
{ {
'--sidebar-width': SIDEBAR_WIDTH, '--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON, '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
'--sidebar-width-icon-extended':
SIDEBAR_WIDTH_ICON_EXTENDED,
...style, ...style,
} as React.CSSProperties } as React.CSSProperties
} }
@@ -166,7 +169,7 @@ const Sidebar = React.forwardRef<
React.ComponentProps<'div'> & { React.ComponentProps<'div'> & {
side?: 'left' | 'right'; side?: 'left' | 'right';
variant?: 'sidebar' | 'floating' | 'inset'; variant?: 'sidebar' | 'floating' | 'inset';
collapsible?: 'offcanvas' | 'icon' | 'none'; collapsible?: 'offcanvas' | 'icon' | 'icon-extended' | 'none';
} }
>( >(
( (
@@ -245,8 +248,8 @@ const Sidebar = React.forwardRef<
'group-data-[collapsible=offcanvas]:w-0', 'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180', 'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset' variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]' ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]' : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended]'
)} )}
/> />
<div <div
@@ -257,8 +260,8 @@ const Sidebar = React.forwardRef<
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]', : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants. // Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset' variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]' ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l', : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended] group-data-[side=left]:border-r group-data-[side=right]:border-l',
className className
)} )}
{...props} {...props}
@@ -421,7 +424,7 @@ const SidebarContent = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="content" data-sidebar="content"
className={cn( className={cn(
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden group-data-[collapsible=icon-extended]:overflow-hidden',
className className
)} )}
{...props} {...props}
@@ -461,6 +464,7 @@ const SidebarGroupLabel = React.forwardRef<
className={cn( className={cn(
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', 'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0', 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
'group-data-[collapsible=icon-extended]:-mt-8 group-data-[collapsible=icon-extended]:opacity-0',
className className
)} )}
{...props} {...props}
@@ -483,7 +487,7 @@ const SidebarGroupAction = React.forwardRef<
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', 'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden', 'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden', 'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
className className
)} )}
{...props} {...props}
@@ -532,7 +536,7 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = 'SidebarMenuItem'; SidebarMenuItem.displayName = 'SidebarMenuItem';
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon-extended]:h-auto group-data-[collapsible=icon-extended]:flex-col group-data-[collapsible=icon-extended]:gap-1 group-data-[collapsible=icon-extended]:p-2 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate group-data-[collapsible=icon-extended]:[&>span]:w-full group-data-[collapsible=icon-extended]:[&>span]:text-center group-data-[collapsible=icon-extended]:[&>span]:text-[10px] group-data-[collapsible=icon-extended]:[&>span]:leading-tight [&>svg]:size-4 [&>svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {
@@ -636,7 +640,7 @@ const SidebarMenuAction = React.forwardRef<
'peer-data-[size=sm]/menu-button:top-1', 'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5', 'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5', 'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden', 'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
showOnHover && showOnHover &&
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0', 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
className className
@@ -753,7 +757,7 @@ const SidebarMenuSubButton = React.forwardRef<
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground', 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs', size === 'sm' && 'text-xs',
size === 'md' && 'text-sm', size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden', 'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
className className
)} )}
{...props} {...props}

View File

@@ -13,15 +13,17 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>, React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => ( >(({ className, sideOffset = 4, ...props }, ref) => (
// <TooltipPrimitive.Portal>
<TooltipPrimitive.Content <TooltipPrimitive.Content
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]',
className className
)} )}
{...props} {...props}
/> />
// </TooltipPrimitive.Portal>
)); ));
TooltipContent.displayName = TooltipPrimitive.Content.displayName; TooltipContent.displayName = TooltipPrimitive.Content.displayName;

View File

@@ -42,6 +42,7 @@ interface TreeViewProps<
renderHoverComponent?: (node: TreeNode<Type, Context>) => ReactNode; renderHoverComponent?: (node: TreeNode<Type, Context>) => ReactNode;
renderActionsComponent?: (node: TreeNode<Type, Context>) => ReactNode; renderActionsComponent?: (node: TreeNode<Type, Context>) => ReactNode;
loadingNodeIds?: string[]; loadingNodeIds?: string[];
disableCache?: boolean;
} }
export function TreeView< export function TreeView<
@@ -62,12 +63,14 @@ export function TreeView<
renderHoverComponent, renderHoverComponent,
renderActionsComponent, renderActionsComponent,
loadingNodeIds, loadingNodeIds,
disableCache = false,
}: TreeViewProps<Type, Context>) { }: TreeViewProps<Type, Context>) {
const { expanded, loading, loadedChildren, hasMoreChildren, toggleNode } = const { expanded, loading, loadedChildren, hasMoreChildren, toggleNode } =
useTree({ useTree({
fetchChildren, fetchChildren,
expanded: expandedProp, expanded: expandedProp,
setExpanded: setExpandedProp, setExpanded: setExpandedProp,
disableCache,
}); });
const [selectedIdInternal, setSelectedIdInternal] = React.useState< const [selectedIdInternal, setSelectedIdInternal] = React.useState<
string | undefined string | undefined
@@ -145,6 +148,7 @@ export function TreeView<
renderHoverComponent={renderHoverComponent} renderHoverComponent={renderHoverComponent}
renderActionsComponent={renderActionsComponent} renderActionsComponent={renderActionsComponent}
loadingNodeIds={loadingNodeIds} loadingNodeIds={loadingNodeIds}
disableCache={disableCache}
/> />
))} ))}
</div> </div>
@@ -179,6 +183,7 @@ interface TreeNodeProps<
renderHoverComponent?: (node: TreeNode<Type, Context>) => ReactNode; renderHoverComponent?: (node: TreeNode<Type, Context>) => ReactNode;
renderActionsComponent?: (node: TreeNode<Type, Context>) => ReactNode; renderActionsComponent?: (node: TreeNode<Type, Context>) => ReactNode;
loadingNodeIds?: string[]; loadingNodeIds?: string[];
disableCache?: boolean;
} }
function TreeNode<Type extends string, Context extends Record<Type, unknown>>({ function TreeNode<Type extends string, Context extends Record<Type, unknown>>({
@@ -201,11 +206,16 @@ function TreeNode<Type extends string, Context extends Record<Type, unknown>>({
renderHoverComponent, renderHoverComponent,
renderActionsComponent, renderActionsComponent,
loadingNodeIds, loadingNodeIds,
disableCache = false,
}: TreeNodeProps<Type, Context>) { }: TreeNodeProps<Type, Context>) {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const isExpanded = expanded[node.id]; const isExpanded = expanded[node.id];
const isLoading = loading[node.id]; const isLoading = loading[node.id];
const children = loadedChildren[node.id] || node.children; // If cache is disabled, always use fresh node.children
// Otherwise, use cached loadedChildren if available (for async fetched data)
const children = disableCache
? node.children
: node.children || loadedChildren[node.id];
const isSelected = selectedId === node.id; const isSelected = selectedId === node.id;
const IconComponent = const IconComponent =
@@ -423,6 +433,7 @@ function TreeNode<Type extends string, Context extends Record<Type, unknown>>({
renderHoverComponent={renderHoverComponent} renderHoverComponent={renderHoverComponent}
renderActionsComponent={renderActionsComponent} renderActionsComponent={renderActionsComponent}
loadingNodeIds={loadingNodeIds} loadingNodeIds={loadingNodeIds}
disableCache={disableCache}
/> />
))} ))}
{isLoading ? ( {isLoading ? (

View File

@@ -28,10 +28,12 @@ export function useTree<
fetchChildren, fetchChildren,
expanded: expandedProp, expanded: expandedProp,
setExpanded: setExpandedProp, setExpanded: setExpandedProp,
disableCache = false,
}: { }: {
fetchChildren?: FetchChildrenFunction<Type, Context>; fetchChildren?: FetchChildrenFunction<Type, Context>;
expanded?: ExpandedState; expanded?: ExpandedState;
setExpanded?: Dispatch<SetStateAction<ExpandedState>>; setExpanded?: Dispatch<SetStateAction<ExpandedState>>;
disableCache?: boolean;
}) { }) {
const [expandedInternal, setExpandedInternal] = useState<ExpandedState>({}); const [expandedInternal, setExpandedInternal] = useState<ExpandedState>({});
@@ -89,8 +91,8 @@ export function useTree<
// Get any previously fetched children // Get any previously fetched children
const previouslyFetchedChildren = loadedChildren[nodeId] || []; const previouslyFetchedChildren = loadedChildren[nodeId] || [];
// If we have static children, merge them with any previously fetched children // Only cache if caching is enabled
if (staticChildren?.length) { if (!disableCache && staticChildren?.length) {
const mergedChildren = mergeChildren( const mergedChildren = mergeChildren(
staticChildren, staticChildren,
previouslyFetchedChildren previouslyFetchedChildren
@@ -110,8 +112,8 @@ export function useTree<
// Set expanded state immediately to show static/previously fetched children // Set expanded state immediately to show static/previously fetched children
setExpanded((prev) => ({ ...prev, [nodeId]: true })); setExpanded((prev) => ({ ...prev, [nodeId]: true }));
// If we haven't loaded dynamic children yet // If we haven't loaded dynamic children yet and cache is enabled
if (!previouslyFetchedChildren.length) { if (!disableCache && !previouslyFetchedChildren.length) {
setLoading((prev) => ({ ...prev, [nodeId]: true })); setLoading((prev) => ({ ...prev, [nodeId]: true }));
try { try {
const fetchedChildren = await fetchChildren?.( const fetchedChildren = await fetchChildren?.(
@@ -140,7 +142,14 @@ export function useTree<
} }
} }
}, },
[expanded, loadedChildren, fetchChildren, mergeChildren, setExpanded] [
expanded,
loadedChildren,
fetchChildren,
mergeChildren,
setExpanded,
disableCache,
]
); );
return { return {

View File

@@ -2,6 +2,24 @@ import { createContext } from 'react';
import { emptyFn } from '@/lib/utils'; import { emptyFn } from '@/lib/utils';
import type { Graph } from '@/lib/graph'; import type { Graph } from '@/lib/graph';
import { createGraph } from '@/lib/graph'; import { createGraph } from '@/lib/graph';
import { EventEmitter } from 'ahooks/lib/useEventEmitter';
export type CanvasEventType = 'pan_click';
export type CanvasEventBase<T extends CanvasEventType, D> = {
action: T;
data: D;
};
export type PanClickEvent = CanvasEventBase<
'pan_click',
{
x: number;
y: number;
}
>;
export type CanvasEvent = PanClickEvent;
export interface CanvasContext { export interface CanvasContext {
reorderTables: (options?: { updateHistory?: boolean }) => void; reorderTables: (options?: { updateHistory?: boolean }) => void;
@@ -14,6 +32,42 @@ export interface CanvasContext {
overlapGraph: Graph<string>; overlapGraph: Graph<string>;
setShowFilter: React.Dispatch<React.SetStateAction<boolean>>; setShowFilter: React.Dispatch<React.SetStateAction<boolean>>;
showFilter: boolean; showFilter: boolean;
editTableModeTable: {
tableId: string;
fieldId?: string;
} | null;
setEditTableModeTable: React.Dispatch<
React.SetStateAction<{
tableId: string;
fieldId?: string;
} | null>
>;
tempFloatingEdge: {
sourceNodeId: string;
targetNodeId?: string;
} | null;
setTempFloatingEdge: React.Dispatch<
React.SetStateAction<{
sourceNodeId: string;
targetNodeId?: string;
} | null>
>;
startFloatingEdgeCreation: ({
sourceNodeId,
}: {
sourceNodeId: string;
}) => void;
endFloatingEdgeCreation: () => void;
hoveringTableId: string | null;
setHoveringTableId: React.Dispatch<React.SetStateAction<string | null>>;
showCreateRelationshipNode: (params: {
sourceTableId: string;
targetTableId: string;
x: number;
y: number;
}) => void;
hideCreateRelationshipNode: () => void;
events: EventEmitter<CanvasEvent>;
} }
export const canvasContext = createContext<CanvasContext>({ export const canvasContext = createContext<CanvasContext>({
@@ -23,4 +77,15 @@ export const canvasContext = createContext<CanvasContext>({
overlapGraph: createGraph(), overlapGraph: createGraph(),
setShowFilter: emptyFn, setShowFilter: emptyFn,
showFilter: false, showFilter: false,
editTableModeTable: null,
setEditTableModeTable: emptyFn,
tempFloatingEdge: null,
setTempFloatingEdge: emptyFn,
startFloatingEdgeCreation: emptyFn,
endFloatingEdgeCreation: emptyFn,
hoveringTableId: null,
setHoveringTableId: emptyFn,
showCreateRelationshipNode: emptyFn,
hideCreateRelationshipNode: emptyFn,
events: new EventEmitter(),
}); });

View File

@@ -1,28 +1,74 @@
import React, { type ReactNode, useCallback, useState } from 'react'; import React, {
type ReactNode,
useCallback,
useState,
useEffect,
useRef,
} from 'react';
import type { CanvasContext, CanvasEvent } from './canvas-context';
import { canvasContext } from './canvas-context'; import { canvasContext } from './canvas-context';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { import { adjustTablePositions } from '@/lib/domain/db-table';
adjustTablePositions,
shouldShowTablesBySchemaFilter,
} from '@/lib/domain/db-table';
import { useReactFlow } from '@xyflow/react'; import { useReactFlow } from '@xyflow/react';
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils'; import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
import type { Graph } from '@/lib/graph'; import type { Graph } from '@/lib/graph';
import { createGraph } from '@/lib/graph'; import { createGraph } from '@/lib/graph';
import { useDiagramFilter } from '../diagram-filter-context/use-diagram-filter';
import { filterTable } from '@/lib/domain/diagram-filter/filter';
import { defaultSchemas } from '@/lib/data/default-schemas';
import {
CREATE_RELATIONSHIP_NODE_ID,
type CreateRelationshipNodeType,
} from '@/pages/editor-page/canvas/create-relationship-node/create-relationship-node';
import { useEventEmitter } from 'ahooks';
interface CanvasProviderProps { interface CanvasProviderProps {
children: ReactNode; children: ReactNode;
} }
export const CanvasProvider = ({ children }: CanvasProviderProps) => { export const CanvasProvider = ({ children }: CanvasProviderProps) => {
const { tables, relationships, updateTablesState, filteredSchemas } = const {
useChartDB(); tables,
const { fitView } = useReactFlow(); relationships,
updateTablesState,
databaseType,
areas,
diagramId,
} = useChartDB();
const { filter, loading: filterLoading } = useDiagramFilter();
const { fitView, screenToFlowPosition, setNodes } = useReactFlow();
const [overlapGraph, setOverlapGraph] = const [overlapGraph, setOverlapGraph] =
useState<Graph<string>>(createGraph()); useState<Graph<string>>(createGraph());
const [editTableModeTable, setEditTableModeTable] = useState<{
tableId: string;
fieldId?: string;
} | null>(null);
const events = useEventEmitter<CanvasEvent>();
const [showFilter, setShowFilter] = useState(false); const [showFilter, setShowFilter] = useState(false);
const [tempFloatingEdge, setTempFloatingEdge] =
useState<CanvasContext['tempFloatingEdge']>(null);
const [hoveringTableId, setHoveringTableId] = useState<string | null>(null);
const diagramIdActiveFilterRef = useRef<string>();
useEffect(() => {
if (filterLoading) {
return;
}
if (diagramIdActiveFilterRef.current === diagramId) {
return;
}
diagramIdActiveFilterRef.current = diagramId;
setShowFilter(true);
}, [filterLoading, diagramId]);
const reorderTables = useCallback( const reorderTables = useCallback(
( (
options: { updateHistory?: boolean } = { options: { updateHistory?: boolean } = {
@@ -32,9 +78,19 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
const newTables = adjustTablePositions({ const newTables = adjustTablePositions({
relationships, relationships,
tables: tables.filter((table) => tables: tables.filter((table) =>
shouldShowTablesBySchemaFilter(table, filteredSchemas) filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[databaseType],
},
})
), ),
mode: 'all', // Use 'all' mode for manual reordering areas,
mode: 'all',
}); });
const updatedOverlapGraph = findOverlappingTables({ const updatedOverlapGraph = findOverlappingTables({
@@ -69,9 +125,77 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
}); });
}, 500); }, 500);
}, },
[filteredSchemas, relationships, tables, updateTablesState, fitView] [
filter,
relationships,
tables,
updateTablesState,
fitView,
databaseType,
areas,
]
); );
const startFloatingEdgeCreation: CanvasContext['startFloatingEdgeCreation'] =
useCallback(({ sourceNodeId }) => {
setShowFilter(false);
setTempFloatingEdge({
sourceNodeId,
});
}, []);
const endFloatingEdgeCreation: CanvasContext['endFloatingEdgeCreation'] =
useCallback(() => {
setTempFloatingEdge(null);
}, []);
const hideCreateRelationshipNode: CanvasContext['hideCreateRelationshipNode'] =
useCallback(() => {
setNodes((nds) =>
nds.filter((n) => n.id !== CREATE_RELATIONSHIP_NODE_ID)
);
endFloatingEdgeCreation();
}, [setNodes, endFloatingEdgeCreation]);
const showCreateRelationshipNode: CanvasContext['showCreateRelationshipNode'] =
useCallback(
({ sourceTableId, targetTableId, x, y }) => {
setTempFloatingEdge((edge) =>
edge
? {
...edge,
targetNodeId: targetTableId,
}
: null
);
const cursorPos = screenToFlowPosition({
x,
y,
});
const newNode: CreateRelationshipNodeType = {
id: CREATE_RELATIONSHIP_NODE_ID,
type: 'create-relationship',
position: cursorPos,
data: {
sourceTableId,
targetTableId,
},
draggable: true,
selectable: false,
zIndex: 1000,
};
setNodes((nds) => {
const nodesWithoutOldCreateRelationshipNode = nds.filter(
(n) => n.id !== CREATE_RELATIONSHIP_NODE_ID
);
return [...nodesWithoutOldCreateRelationshipNode, newNode];
});
},
[screenToFlowPosition, setNodes]
);
return ( return (
<canvasContext.Provider <canvasContext.Provider
value={{ value={{
@@ -81,6 +205,17 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
overlapGraph, overlapGraph,
setShowFilter, setShowFilter,
showFilter, showFilter,
editTableModeTable,
setEditTableModeTable,
tempFloatingEdge: tempFloatingEdge,
setTempFloatingEdge: setTempFloatingEdge,
startFloatingEdgeCreation: startFloatingEdgeCreation,
endFloatingEdgeCreation: endFloatingEdgeCreation,
hoveringTableId,
setHoveringTableId,
showCreateRelationshipNode,
hideCreateRelationshipNode,
events,
}} }}
> >
{children} {children}

View File

@@ -12,6 +12,7 @@ import type { DBDependency } from '@/lib/domain/db-dependency';
import { EventEmitter } from 'ahooks/lib/useEventEmitter'; import { EventEmitter } from 'ahooks/lib/useEventEmitter';
import type { Area } from '@/lib/domain/area'; import type { Area } from '@/lib/domain/area';
import type { DBCustomType } from '@/lib/domain/db-custom-type'; import type { DBCustomType } from '@/lib/domain/db-custom-type';
import type { Note } from '@/lib/domain/note';
export type ChartDBEventType = export type ChartDBEventType =
| 'add_tables' | 'add_tables'
@@ -74,6 +75,7 @@ export interface ChartDBContext {
dependencies: DBDependency[]; dependencies: DBDependency[];
areas: Area[]; areas: Area[];
customTypes: DBCustomType[]; customTypes: DBCustomType[];
notes: Note[];
currentDiagram: Diagram; currentDiagram: Diagram;
events: EventEmitter<ChartDBEvent>; events: EventEmitter<ChartDBEvent>;
readonly?: boolean; readonly?: boolean;
@@ -81,9 +83,6 @@ export interface ChartDBContext {
highlightedCustomType?: DBCustomType; highlightedCustomType?: DBCustomType;
highlightCustomTypeId: (id?: string) => void; highlightCustomTypeId: (id?: string) => void;
filteredSchemas?: string[];
filterSchemas: (schemaIds: string[]) => void;
// General operations // General operations
updateDiagramId: (id: string) => Promise<void>; updateDiagramId: (id: string) => Promise<void>;
updateDiagramName: ( updateDiagramName: (
@@ -258,6 +257,31 @@ export interface ChartDBContext {
options?: { updateHistory: boolean } options?: { updateHistory: boolean }
) => Promise<void>; ) => Promise<void>;
// Note operations
createNote: (attributes?: Partial<Omit<Note, 'id'>>) => Promise<Note>;
addNote: (
note: Note,
options?: { updateHistory: boolean }
) => Promise<void>;
addNotes: (
notes: Note[],
options?: { updateHistory: boolean }
) => Promise<void>;
getNote: (id: string) => Note | null;
removeNote: (
id: string,
options?: { updateHistory: boolean }
) => Promise<void>;
removeNotes: (
ids: string[],
options?: { updateHistory: boolean }
) => Promise<void>;
updateNote: (
id: string,
note: Partial<Note>,
options?: { updateHistory: boolean }
) => Promise<void>;
// Custom type operations // Custom type operations
createCustomType: ( createCustomType: (
attributes?: Partial<Omit<DBCustomType, 'id'>> attributes?: Partial<Omit<DBCustomType, 'id'>>
@@ -284,11 +308,6 @@ export interface ChartDBContext {
customType: Partial<DBCustomType>, customType: Partial<DBCustomType>,
options?: { updateHistory: boolean } options?: { updateHistory: boolean }
) => Promise<void>; ) => Promise<void>;
// Filters
hiddenTableIds?: string[];
addHiddenTableId: (tableId: string) => Promise<void>;
removeHiddenTableId: (tableId: string) => Promise<void>;
} }
export const chartDBContext = createContext<ChartDBContext>({ export const chartDBContext = createContext<ChartDBContext>({
@@ -300,10 +319,9 @@ export const chartDBContext = createContext<ChartDBContext>({
dependencies: [], dependencies: [],
areas: [], areas: [],
customTypes: [], customTypes: [],
notes: [],
schemas: [], schemas: [],
highlightCustomTypeId: emptyFn, highlightCustomTypeId: emptyFn,
filteredSchemas: [],
filterSchemas: emptyFn,
currentDiagram: { currentDiagram: {
id: '', id: '',
name: '', name: '',
@@ -378,6 +396,15 @@ export const chartDBContext = createContext<ChartDBContext>({
removeAreas: emptyFn, removeAreas: emptyFn,
updateArea: emptyFn, updateArea: emptyFn,
// Note operations
createNote: emptyFn,
addNote: emptyFn,
addNotes: emptyFn,
getNote: emptyFn,
removeNote: emptyFn,
removeNotes: emptyFn,
updateNote: emptyFn,
// Custom type operations // Custom type operations
createCustomType: emptyFn, createCustomType: emptyFn,
addCustomType: emptyFn, addCustomType: emptyFn,
@@ -386,9 +413,4 @@ export const chartDBContext = createContext<ChartDBContext>({
removeCustomType: emptyFn, removeCustomType: emptyFn,
removeCustomTypes: emptyFn, removeCustomTypes: emptyFn,
updateCustomType: emptyFn, updateCustomType: emptyFn,
// Filters
hiddenTableIds: [],
addHiddenTableId: emptyFn,
removeHiddenTableId: emptyFn,
}); });

View File

@@ -1,12 +1,15 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import type { DBTable } from '@/lib/domain/db-table'; import type { DBTable } from '@/lib/domain/db-table';
import { deepCopy, generateId } from '@/lib/utils'; import { deepCopy, generateId } from '@/lib/utils';
import { randomColor } from '@/lib/colors'; import { defaultTableColor, defaultAreaColor, viewColor } from '@/lib/colors';
import type { ChartDBContext, ChartDBEvent } from './chartdb-context'; import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
import { chartDBContext } from './chartdb-context'; import { chartDBContext } from './chartdb-context';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import type { DBField } from '@/lib/domain/db-field'; import type { DBField } from '@/lib/domain/db-field';
import type { DBIndex } from '@/lib/domain/db-index'; import {
getTableIndexesWithPrimaryKey,
type DBIndex,
} from '@/lib/domain/db-index';
import type { DBRelationship } from '@/lib/domain/db-relationship'; import type { DBRelationship } from '@/lib/domain/db-relationship';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack'; import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
@@ -17,11 +20,11 @@ import {
databasesWithSchemas, databasesWithSchemas,
schemaNameToSchemaId, schemaNameToSchemaId,
} from '@/lib/domain/db-schema'; } from '@/lib/domain/db-schema';
import { useLocalConfig } from '@/hooks/use-local-config';
import { defaultSchemas } from '@/lib/data/default-schemas'; import { defaultSchemas } from '@/lib/data/default-schemas';
import { useEventEmitter } from 'ahooks'; import { useEventEmitter } from 'ahooks';
import type { DBDependency } from '@/lib/domain/db-dependency'; import type { DBDependency } from '@/lib/domain/db-dependency';
import type { Area } from '@/lib/domain/area'; import type { Area } from '@/lib/domain/area';
import type { Note } from '@/lib/domain/note';
import { storageInitialValue } from '../storage-context/storage-context'; import { storageInitialValue } from '../storage-context/storage-context';
import { useDiff } from '../diff-context/use-diff'; import { useDiff } from '../diff-context/use-diff';
import type { DiffCalculatedEvent } from '../diff-context/diff-context'; import type { DiffCalculatedEvent } from '../diff-context/diff-context';
@@ -29,7 +32,6 @@ import {
DBCustomTypeKind, DBCustomTypeKind,
type DBCustomType, type DBCustomType,
} from '@/lib/domain/db-custom-type'; } from '@/lib/domain/db-custom-type';
import { useConfig } from '@/hooks/use-config';
export interface ChartDBProviderProps { export interface ChartDBProviderProps {
diagram?: Diagram; diagram?: Diagram;
@@ -40,17 +42,11 @@ export const ChartDBProvider: React.FC<
React.PropsWithChildren<ChartDBProviderProps> React.PropsWithChildren<ChartDBProviderProps>
> = ({ children, diagram, readonly: readonlyProp }) => { > = ({ children, diagram, readonly: readonlyProp }) => {
const { hasDiff } = useDiff(); const { hasDiff } = useDiff();
const dbStorage = useStorage(); const storageDB = useStorage();
let db = dbStorage;
const events = useEventEmitter<ChartDBEvent>(); const events = useEventEmitter<ChartDBEvent>();
const { setSchemasFilter, schemasFilter } = useLocalConfig();
const { addUndoAction, resetRedoStack, resetUndoStack } = const { addUndoAction, resetRedoStack, resetUndoStack } =
useRedoUndoStack(); useRedoUndoStack();
const {
getHiddenTablesForDiagram,
hideTableForDiagram,
unhideTableForDiagram,
} = useConfig();
const [diagramId, setDiagramId] = useState(''); const [diagramId, setDiagramId] = useState('');
const [diagramName, setDiagramName] = useState(''); const [diagramName, setDiagramName] = useState('');
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date()); const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
@@ -72,17 +68,18 @@ export const ChartDBProvider: React.FC<
const [customTypes, setCustomTypes] = useState<DBCustomType[]>( const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
diagram?.customTypes ?? [] diagram?.customTypes ?? []
); );
const [hiddenTableIds, setHiddenTableIds] = useState<string[]>([]); const [notes, setNotes] = useState<Note[]>(diagram?.notes ?? []);
const { events: diffEvents } = useDiff(); const { events: diffEvents } = useDiff();
const [highlightedCustomTypeId, setHighlightedCustomTypeId] = const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
useState<string>(); useState<string>();
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => { const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
const { tablesAdded, fieldsAdded, relationshipsAdded } = event.data; const { tablesToAdd, fieldsToAdd, relationshipsToAdd } = event.data;
setTables((tables) => setTables((tables) =>
[...tables, ...(tablesAdded ?? [])].map((table) => { [...tables, ...(tablesToAdd ?? [])].map((table) => {
const fields = fieldsAdded.get(table.id); const fields = fieldsToAdd.get(table.id);
return fields return fields
? { ...table, fields: [...table.fields, ...fields] } ? { ...table, fields: [...table.fields, ...fields] }
: table; : table;
@@ -90,31 +87,22 @@ export const ChartDBProvider: React.FC<
); );
setRelationships((relationships) => [ setRelationships((relationships) => [
...relationships, ...relationships,
...(relationshipsAdded ?? []), ...(relationshipsToAdd ?? []),
]); ]);
}, []); }, []);
diffEvents.useSubscription(diffCalculatedHandler); diffEvents.useSubscription(diffCalculatedHandler);
// Sync hiddenTableIds with config const defaultSchemaName = useMemo(
useEffect(() => { () => defaultSchemas[databaseType],
if (diagramId) { [databaseType]
const hiddenTables = getHiddenTablesForDiagram(diagramId); );
setHiddenTableIds(hiddenTables);
}
}, [diagramId, getHiddenTablesForDiagram]);
const defaultSchemaName = defaultSchemas[databaseType];
const readonly = useMemo( const readonly = useMemo(
() => readonlyProp ?? hasDiff ?? false, () => readonlyProp ?? hasDiff ?? false,
[readonlyProp, hasDiff] [readonlyProp, hasDiff]
); );
if (readonly) {
db = storageInitialValue;
}
const schemas = useMemo( const schemas = useMemo(
() => () =>
databasesWithSchemas.includes(databaseType) databasesWithSchemas.includes(databaseType)
@@ -125,9 +113,11 @@ export const ChartDBProvider: React.FC<
.filter((schema) => !!schema) as string[] .filter((schema) => !!schema) as string[]
), ),
] ]
.sort((a, b) => .sort((a, b) => {
a === defaultSchemaName ? -1 : a.localeCompare(b) if (a === defaultSchemaName) return -1;
) if (b === defaultSchemaName) return 1;
return a.localeCompare(b);
})
.map( .map(
(schema): DBSchema => ({ (schema): DBSchema => ({
id: schemaNameToSchemaId(schema), id: schemaNameToSchemaId(schema),
@@ -141,34 +131,11 @@ export const ChartDBProvider: React.FC<
[tables, defaultSchemaName, databaseType] [tables, defaultSchemaName, databaseType]
); );
const filterSchemas: ChartDBContext['filterSchemas'] = useCallback( const db = useMemo(
(schemaIds) => { () => (readonly ? storageInitialValue : storageDB),
setSchemasFilter((prev) => ({ [storageDB, readonly]
...prev,
[diagramId]: schemaIds,
}));
},
[diagramId, setSchemasFilter]
); );
const filteredSchemas: ChartDBContext['filteredSchemas'] = useMemo(() => {
if (schemas.length === 0) {
return undefined;
}
const schemasFilterFromCache =
(schemasFilter[diagramId] ?? []).length === 0
? undefined // in case of empty filter, skip cache
: schemasFilter[diagramId];
return (
schemasFilterFromCache ?? [
schemas.find((s) => s.name === defaultSchemaName)?.id ??
schemas[0]?.id,
]
);
}, [schemasFilter, diagramId, schemas, defaultSchemaName]);
const currentDiagram: Diagram = useMemo( const currentDiagram: Diagram = useMemo(
() => ({ () => ({
id: diagramId, id: diagramId,
@@ -182,6 +149,7 @@ export const ChartDBProvider: React.FC<
dependencies, dependencies,
areas, areas,
customTypes, customTypes,
notes,
}), }),
[ [
diagramId, diagramId,
@@ -193,6 +161,7 @@ export const ChartDBProvider: React.FC<
dependencies, dependencies,
areas, areas,
customTypes, customTypes,
notes,
diagramCreatedAt, diagramCreatedAt,
diagramUpdatedAt, diagramUpdatedAt,
] ]
@@ -206,6 +175,7 @@ export const ChartDBProvider: React.FC<
setDependencies([]); setDependencies([]);
setAreas([]); setAreas([]);
setCustomTypes([]); setCustomTypes([]);
setNotes([]);
setDiagramUpdatedAt(updatedAt); setDiagramUpdatedAt(updatedAt);
resetRedoStack(); resetRedoStack();
@@ -218,6 +188,7 @@ export const ChartDBProvider: React.FC<
db.deleteDiagramDependencies(diagramId), db.deleteDiagramDependencies(diagramId),
db.deleteDiagramAreas(diagramId), db.deleteDiagramAreas(diagramId),
db.deleteDiagramCustomTypes(diagramId), db.deleteDiagramCustomTypes(diagramId),
db.deleteDiagramNotes(diagramId),
]); ]);
}, [db, diagramId, resetRedoStack, resetUndoStack]); }, [db, diagramId, resetRedoStack, resetUndoStack]);
@@ -232,6 +203,7 @@ export const ChartDBProvider: React.FC<
setDependencies([]); setDependencies([]);
setAreas([]); setAreas([]);
setCustomTypes([]); setCustomTypes([]);
setNotes([]);
resetRedoStack(); resetRedoStack();
resetUndoStack(); resetUndoStack();
@@ -242,6 +214,7 @@ export const ChartDBProvider: React.FC<
db.deleteDiagramDependencies(diagramId), db.deleteDiagramDependencies(diagramId),
db.deleteDiagramAreas(diagramId), db.deleteDiagramAreas(diagramId),
db.deleteDiagramCustomTypes(diagramId), db.deleteDiagramCustomTypes(diagramId),
db.deleteDiagramNotes(diagramId),
]); ]);
}, [db, diagramId, resetRedoStack, resetUndoStack]); }, [db, diagramId, resetRedoStack, resetUndoStack]);
@@ -380,12 +353,18 @@ export const ChartDBProvider: React.FC<
}, },
], ],
indexes: [], indexes: [],
color: randomColor(), color: attributes?.isView ? viewColor : defaultTableColor,
createdAt: Date.now(), createdAt: Date.now(),
isView: false, isView: false,
order: tables.length, order: tables.length,
...attributes, ...attributes,
schema: attributes?.schema ?? defaultSchemas[databaseType],
}; };
table.indexes = getTableIndexesWithPrimaryKey({
table,
});
await addTable(table); await addTable(table);
return table; return table;
@@ -677,17 +656,30 @@ export const ChartDBProvider: React.FC<
options = { updateHistory: true } options = { updateHistory: true }
) => { ) => {
const prevField = getField(tableId, fieldId); const prevField = getField(tableId, fieldId);
const updateTableFn = (table: DBTable) => {
const updatedTable: DBTable = {
...table,
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
} satisfies DBTable;
updatedTable.indexes = getTableIndexesWithPrimaryKey({
table: updatedTable,
});
return updatedTable;
};
setTables((tables) => setTables((tables) =>
tables.map((table) => tables.map((table) => {
table.id === tableId if (table.id === tableId) {
? { return updateTableFn(table);
...table, }
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f return table;
), })
}
: table
)
); );
const table = await db.getTable({ diagramId, id: tableId }); const table = await db.getTable({ diagramId, id: tableId });
@@ -702,10 +694,7 @@ export const ChartDBProvider: React.FC<
db.updateTable({ db.updateTable({
id: tableId, id: tableId,
attributes: { attributes: {
...table, ...updateTableFn(table),
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
}, },
}), }),
]); ]);
@@ -732,19 +721,29 @@ export const ChartDBProvider: React.FC<
fieldId: string, fieldId: string,
options = { updateHistory: true } options = { updateHistory: true }
) => { ) => {
const updateTableFn = (table: DBTable) => {
const updatedTable: DBTable = {
...table,
fields: table.fields.filter((f) => f.id !== fieldId),
} satisfies DBTable;
updatedTable.indexes = getTableIndexesWithPrimaryKey({
table: updatedTable,
});
return updatedTable;
};
const fields = getTable(tableId)?.fields ?? []; const fields = getTable(tableId)?.fields ?? [];
const prevField = getField(tableId, fieldId); const prevField = getField(tableId, fieldId);
setTables((tables) => setTables((tables) =>
tables.map((table) => tables.map((table) => {
table.id === tableId if (table.id === tableId) {
? { return updateTableFn(table);
...table, }
fields: table.fields.filter(
(f) => f.id !== fieldId return table;
), })
}
: table
)
); );
events.emit({ events.emit({
@@ -768,8 +767,7 @@ export const ChartDBProvider: React.FC<
db.updateTable({ db.updateTable({
id: tableId, id: tableId,
attributes: { attributes: {
...table, ...updateTableFn(table),
fields: table.fields.filter((f) => f.id !== fieldId),
}, },
}), }),
]); ]);
@@ -1125,12 +1123,15 @@ export const ChartDBProvider: React.FC<
const sourceFieldName = sourceField?.name ?? ''; const sourceFieldName = sourceField?.name ?? '';
const targetTable = getTable(targetTableId);
const targetTableSchema = targetTable?.schema;
const relationship: DBRelationship = { const relationship: DBRelationship = {
id: generateId(), id: generateId(),
name: `${sourceTableName}_${sourceFieldName}_fk`, name: `${sourceTableName}_${sourceFieldName}_fk`,
sourceSchema: sourceTable?.schema, sourceSchema: sourceTable?.schema,
sourceTableId, sourceTableId,
targetSchema: sourceTable?.schema, targetSchema: targetTableSchema,
targetTableId, targetTableId,
sourceFieldId, sourceFieldId,
targetFieldId, targetFieldId,
@@ -1452,7 +1453,7 @@ export const ChartDBProvider: React.FC<
y: 0, y: 0,
width: 300, width: 300,
height: 200, height: 200,
color: randomColor(), color: defaultAreaColor,
...attributes, ...attributes,
}; };
@@ -1535,6 +1536,130 @@ export const ChartDBProvider: React.FC<
[db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack] [db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack]
); );
// Note operations
const addNotes: ChartDBContext['addNotes'] = useCallback(
async (notes: Note[], options = { updateHistory: true }) => {
setNotes((currentNotes) => [...currentNotes, ...notes]);
const updatedAt = new Date();
setDiagramUpdatedAt(updatedAt);
await Promise.all([
...notes.map((note) => db.addNote({ diagramId, note })),
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
]);
if (options.updateHistory) {
addUndoAction({
action: 'addNotes',
redoData: { notes },
undoData: { noteIds: notes.map((n) => n.id) },
});
resetRedoStack();
}
},
[db, diagramId, setNotes, addUndoAction, resetRedoStack]
);
const addNote: ChartDBContext['addNote'] = useCallback(
async (note: Note, options = { updateHistory: true }) => {
return addNotes([note], options);
},
[addNotes]
);
const createNote: ChartDBContext['createNote'] = useCallback(
async (attributes) => {
const note: Note = {
id: generateId(),
content: '',
x: 0,
y: 0,
width: 200,
height: 150,
color: '#ffe374', // Default warm yellow
...attributes,
};
await addNote(note);
return note;
},
[addNote]
);
const getNote: ChartDBContext['getNote'] = useCallback(
(id: string) => notes.find((note) => note.id === id) ?? null,
[notes]
);
const removeNotes: ChartDBContext['removeNotes'] = useCallback(
async (ids: string[], options = { updateHistory: true }) => {
const prevNotes = [
...notes.filter((note) => ids.includes(note.id)),
];
setNotes((notes) => notes.filter((note) => !ids.includes(note.id)));
const updatedAt = new Date();
setDiagramUpdatedAt(updatedAt);
await Promise.all([
...ids.map((id) => db.deleteNote({ diagramId, id })),
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
]);
if (prevNotes.length > 0 && options.updateHistory) {
addUndoAction({
action: 'removeNotes',
redoData: { noteIds: ids },
undoData: { notes: prevNotes },
});
resetRedoStack();
}
},
[db, diagramId, setNotes, notes, addUndoAction, resetRedoStack]
);
const removeNote: ChartDBContext['removeNote'] = useCallback(
async (id: string, options = { updateHistory: true }) => {
return removeNotes([id], options);
},
[removeNotes]
);
const updateNote: ChartDBContext['updateNote'] = useCallback(
async (
id: string,
note: Partial<Note>,
options = { updateHistory: true }
) => {
const prevNote = getNote(id);
setNotes((notes) =>
notes.map((n) => (n.id === id ? { ...n, ...note } : n))
);
const updatedAt = new Date();
setDiagramUpdatedAt(updatedAt);
await Promise.all([
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
db.updateNote({ id, attributes: note }),
]);
if (!!prevNote && options.updateHistory) {
addUndoAction({
action: 'updateNote',
redoData: { noteId: id, note },
undoData: { noteId: id, note: prevNote },
});
resetRedoStack();
}
},
[db, diagramId, setNotes, getNote, addUndoAction, resetRedoStack]
);
const highlightCustomTypeId = useCallback( const highlightCustomTypeId = useCallback(
(id?: string) => setHighlightedCustomTypeId(id), (id?: string) => setHighlightedCustomTypeId(id),
[setHighlightedCustomTypeId] [setHighlightedCustomTypeId]
@@ -1561,6 +1686,7 @@ export const ChartDBProvider: React.FC<
setDiagramCreatedAt(diagram.createdAt); setDiagramCreatedAt(diagram.createdAt);
setDiagramUpdatedAt(diagram.updatedAt); setDiagramUpdatedAt(diagram.updatedAt);
setHighlightedCustomTypeId(undefined); setHighlightedCustomTypeId(undefined);
setNotes(diagram.notes ?? []);
events.emit({ action: 'load_diagram', data: { diagram } }); events.emit({ action: 'load_diagram', data: { diagram } });
@@ -1581,6 +1707,7 @@ export const ChartDBProvider: React.FC<
setDiagramUpdatedAt, setDiagramUpdatedAt,
setHighlightedCustomTypeId, setHighlightedCustomTypeId,
events, events,
setNotes,
resetRedoStack, resetRedoStack,
resetUndoStack, resetUndoStack,
] ]
@@ -1588,22 +1715,23 @@ export const ChartDBProvider: React.FC<
const updateDiagramData: ChartDBContext['updateDiagramData'] = useCallback( const updateDiagramData: ChartDBContext['updateDiagramData'] = useCallback(
async (diagram, options) => { async (diagram, options) => {
const st = options?.forceUpdateStorage ? dbStorage : db; const st = options?.forceUpdateStorage ? storageDB : db;
await st.deleteDiagram(diagram.id); await st.deleteDiagram(diagram.id);
await st.addDiagram({ diagram }); await st.addDiagram({ diagram });
loadDiagramFromData(diagram); loadDiagramFromData(diagram);
}, },
[db, dbStorage, loadDiagramFromData] [db, storageDB, loadDiagramFromData]
); );
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback( const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
async (diagramId: string) => { async (diagramId: string) => {
const diagram = await db.getDiagram(diagramId, { const diagram = await storageDB.getDiagram(diagramId, {
includeRelationships: true, includeRelationships: true,
includeTables: true, includeTables: true,
includeDependencies: true, includeDependencies: true,
includeAreas: true, includeAreas: true,
includeCustomTypes: true, includeCustomTypes: true,
includeNotes: true,
}); });
if (diagram) { if (diagram) {
@@ -1612,7 +1740,7 @@ export const ChartDBProvider: React.FC<
return diagram; return diagram;
}, },
[db, loadDiagramFromData] [storageDB, loadDiagramFromData]
); );
// Custom type operations // Custom type operations
@@ -1759,29 +1887,6 @@ export const ChartDBProvider: React.FC<
] ]
); );
const addHiddenTableId: ChartDBContext['addHiddenTableId'] = useCallback(
async (tableId: string) => {
if (!hiddenTableIds.includes(tableId)) {
setHiddenTableIds((prev) => [...prev, tableId]);
await hideTableForDiagram(diagramId, tableId);
}
},
[hiddenTableIds, diagramId, hideTableForDiagram]
);
const removeHiddenTableId: ChartDBContext['removeHiddenTableId'] =
useCallback(
async (tableId: string) => {
if (hiddenTableIds.includes(tableId)) {
setHiddenTableIds((prev) =>
prev.filter((id) => id !== tableId)
);
await unhideTableForDiagram(diagramId, tableId);
}
},
[hiddenTableIds, diagramId, unhideTableForDiagram]
);
return ( return (
<chartDBContext.Provider <chartDBContext.Provider
value={{ value={{
@@ -1792,12 +1897,11 @@ export const ChartDBProvider: React.FC<
relationships, relationships,
dependencies, dependencies,
areas, areas,
notes,
currentDiagram, currentDiagram,
schemas, schemas,
filteredSchemas,
events, events,
readonly, readonly,
filterSchemas,
updateDiagramData, updateDiagramData,
updateDiagramId, updateDiagramId,
updateDiagramName, updateDiagramName,
@@ -1855,11 +1959,15 @@ export const ChartDBProvider: React.FC<
removeCustomType, removeCustomType,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
hiddenTableIds,
addHiddenTableId,
removeHiddenTableId,
highlightCustomTypeId, highlightCustomTypeId,
highlightedCustomType, highlightedCustomType,
createNote,
addNote,
addNotes,
getNote,
removeNote,
removeNotes,
updateNote,
}} }}
> >
{children} {children}

View File

@@ -8,23 +8,9 @@ export interface ConfigContext {
config?: Partial<ChartDBConfig>; config?: Partial<ChartDBConfig>;
updateFn?: (config: ChartDBConfig) => ChartDBConfig; updateFn?: (config: ChartDBConfig) => ChartDBConfig;
}) => Promise<void>; }) => Promise<void>;
getHiddenTablesForDiagram: (diagramId: string) => string[];
setHiddenTablesForDiagram: (
diagramId: string,
hiddenTableIds: string[]
) => Promise<void>;
hideTableForDiagram: (diagramId: string, tableId: string) => Promise<void>;
unhideTableForDiagram: (
diagramId: string,
tableId: string
) => Promise<void>;
} }
export const ConfigContext = createContext<ConfigContext>({ export const ConfigContext = createContext<ConfigContext>({
config: undefined, config: undefined,
updateConfig: emptyFn, updateConfig: emptyFn,
getHiddenTablesForDiagram: () => [],
setHiddenTablesForDiagram: emptyFn,
hideTableForDiagram: emptyFn,
unhideTableForDiagram: emptyFn,
}); });

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { ConfigContext } from './config-context'; import { ConfigContext } from './config-context';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
@@ -8,7 +8,7 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
children, children,
}) => { }) => {
const { getConfig, updateConfig: updateDataConfig } = useStorage(); const { getConfig, updateConfig: updateDataConfig } = useStorage();
const [config, setConfig] = React.useState<ChartDBConfig | undefined>(); const [config, setConfig] = useState<ChartDBConfig | undefined>();
useEffect(() => { useEffect(() => {
const loadConfig = async () => { const loadConfig = async () => {
@@ -44,84 +44,11 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
return promise; return promise;
}; };
const getHiddenTablesForDiagram = (diagramId: string): string[] => {
return config?.hiddenTablesByDiagram?.[diagramId] ?? [];
};
const setHiddenTablesForDiagram = async (
diagramId: string,
hiddenTableIds: string[]
): Promise<void> => {
return updateConfig({
updateFn: (currentConfig) => ({
...currentConfig,
hiddenTablesByDiagram: {
...currentConfig.hiddenTablesByDiagram,
[diagramId]: hiddenTableIds,
},
}),
});
};
const hideTableForDiagram = async (
diagramId: string,
tableId: string
): Promise<void> => {
return updateConfig({
updateFn: (currentConfig) => {
const currentHiddenTables =
currentConfig.hiddenTablesByDiagram?.[diagramId] ?? [];
if (currentHiddenTables.includes(tableId)) {
return currentConfig; // Already hidden, no change needed
}
return {
...currentConfig,
hiddenTablesByDiagram: {
...currentConfig.hiddenTablesByDiagram,
[diagramId]: [...currentHiddenTables, tableId],
},
};
},
});
};
const unhideTableForDiagram = async (
diagramId: string,
tableId: string
): Promise<void> => {
return updateConfig({
updateFn: (currentConfig) => {
const currentHiddenTables =
currentConfig.hiddenTablesByDiagram?.[diagramId] ?? [];
const filteredTables = currentHiddenTables.filter(
(id) => id !== tableId
);
if (filteredTables.length === currentHiddenTables.length) {
return currentConfig; // Not hidden, no change needed
}
return {
...currentConfig,
hiddenTablesByDiagram: {
...currentConfig.hiddenTablesByDiagram,
[diagramId]: filteredTables,
},
};
},
});
};
return ( return (
<ConfigContext.Provider <ConfigContext.Provider
value={{ value={{
config, config,
updateConfig, updateConfig,
getHiddenTablesForDiagram,
setHiddenTablesForDiagram,
hideTableForDiagram,
unhideTableForDiagram,
}} }}
> >
{children} {children}

View File

@@ -0,0 +1,50 @@
import type { DBSchema } from '@/lib/domain';
import type {
DiagramFilter,
FilterTableInfo,
} from '@/lib/domain/diagram-filter/diagram-filter';
import { emptyFn } from '@/lib/utils';
import { createContext } from 'react';
export interface DiagramFilterContext {
filter?: DiagramFilter;
loading: boolean;
hasActiveFilter: boolean;
schemasDisplayed: DBSchema[];
clearSchemaIdsFilter: () => void;
clearTableIdsFilter: () => void;
setTableIdsFilterEmpty: () => void;
// reset
resetFilter: () => void;
toggleSchemaFilter: (schemaId: string) => void;
toggleTableFilter: (tableId: string) => void;
addSchemaToFilter: (schemaId: string) => void;
addTablesToFilter: (attrs: {
tableIds?: string[];
filterCallback?: (table: FilterTableInfo) => boolean;
}) => void;
removeTablesFromFilter: (attrs: {
tableIds?: string[];
filterCallback?: (table: FilterTableInfo) => boolean;
}) => void;
}
export const diagramFilterContext = createContext<DiagramFilterContext>({
hasActiveFilter: false,
clearSchemaIdsFilter: emptyFn,
clearTableIdsFilter: emptyFn,
setTableIdsFilterEmpty: emptyFn,
resetFilter: emptyFn,
toggleSchemaFilter: emptyFn,
toggleTableFilter: emptyFn,
addSchemaToFilter: emptyFn,
schemasDisplayed: [],
addTablesToFilter: emptyFn,
removeTablesFromFilter: emptyFn,
loading: false,
});

View File

@@ -0,0 +1,559 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import type { DiagramFilterContext } from './diagram-filter-context';
import { diagramFilterContext } from './diagram-filter-context';
import type {
DiagramFilter,
FilterTableInfo,
} from '@/lib/domain/diagram-filter/diagram-filter';
import {
reduceFilter,
spreadFilterTables,
} from '@/lib/domain/diagram-filter/diagram-filter';
import { useStorage } from '@/hooks/use-storage';
import { useChartDB } from '@/hooks/use-chartdb';
import { filterTable } from '@/lib/domain/diagram-filter/filter';
import { databasesWithSchemas, schemaNameToSchemaId } from '@/lib/domain';
import { defaultSchemas } from '@/lib/data/default-schemas';
import type { ChartDBEvent } from '../chartdb-context/chartdb-context';
export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const { diagramId, tables, schemas, databaseType, events } = useChartDB();
const { getDiagramFilter, updateDiagramFilter } = useStorage();
const [filter, setFilter] = useState<DiagramFilter>({});
const [loading, setLoading] = useState<boolean>(true);
const allSchemasIds = useMemo(() => {
return schemas.map((schema) => schema.id);
}, [schemas]);
const allTables: FilterTableInfo[] = useMemo(() => {
return tables.map(
(table) =>
({
id: table.id,
schemaId: table.schema
? schemaNameToSchemaId(table.schema)
: defaultSchemas[databaseType],
schema: table.schema ?? defaultSchemas[databaseType],
areaId: table.parentAreaId ?? undefined,
}) satisfies FilterTableInfo
);
}, [tables, databaseType]);
const diagramIdOfLoadedFilter = useRef<string | null>(null);
useEffect(() => {
if (diagramId && diagramId === diagramIdOfLoadedFilter.current) {
updateDiagramFilter(diagramId, filter);
}
}, [diagramId, filter, updateDiagramFilter]);
// Reset filter when diagram changes
useEffect(() => {
if (diagramIdOfLoadedFilter.current === diagramId) {
// If the diagramId hasn't changed, do not reset the filter
return;
}
setLoading(true);
const loadFilterFromStorage = async (diagramId: string) => {
if (diagramId) {
const storedFilter = await getDiagramFilter(diagramId);
let filterToSet = storedFilter;
if (!filterToSet) {
// If no filter is stored, set default based on database type
filterToSet =
schemas.length > 1
? { schemaIds: [schemas[0].id] }
: {};
}
setFilter(filterToSet);
}
setLoading(false);
};
setFilter({});
if (diagramId) {
loadFilterFromStorage(diagramId);
diagramIdOfLoadedFilter.current = diagramId;
}
}, [diagramId, getDiagramFilter, schemas]);
const clearSchemaIds: DiagramFilterContext['clearSchemaIdsFilter'] =
useCallback(() => {
setFilter(
(prev) =>
({
...prev,
schemaIds: undefined,
}) satisfies DiagramFilter
);
}, []);
const clearTableIds: DiagramFilterContext['clearTableIdsFilter'] =
useCallback(() => {
setFilter(
(prev) =>
({
...prev,
tableIds: undefined,
}) satisfies DiagramFilter
);
}, []);
const setTableIdsEmpty: DiagramFilterContext['setTableIdsFilterEmpty'] =
useCallback(() => {
setFilter(
(prev) =>
({
...prev,
tableIds: [],
}) satisfies DiagramFilter
);
}, []);
// Reset filter
const resetFilter: DiagramFilterContext['resetFilter'] = useCallback(() => {
setFilter({});
}, []);
const toggleSchemaFilter: DiagramFilterContext['toggleSchemaFilter'] =
useCallback(
(schemaId: string) => {
setFilter((prev) => {
const currentSchemaIds = prev.schemaIds;
// Check if schema is currently visible
const isSchemaVisible = !allTables.some(
(table) =>
table.schemaId === schemaId &&
filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter: prev,
options: {
defaultSchema: defaultSchemas[databaseType],
},
}) === false
);
let newSchemaIds: string[] | undefined;
let newTableIds: string[] | undefined = prev.tableIds;
if (isSchemaVisible) {
// Schema is visible, make it not visible
if (!currentSchemaIds) {
// All schemas are visible, create filter with all except this one
newSchemaIds = allSchemasIds.filter(
(id) => id !== schemaId
);
} else {
// Remove this schema from the filter
newSchemaIds = currentSchemaIds.filter(
(id) => id !== schemaId
);
}
// Remove tables from this schema from tableIds if present
if (prev.tableIds) {
const schemaTableIds = allTables
.filter((table) => table.schemaId === schemaId)
.map((table) => table.id);
newTableIds = prev.tableIds.filter(
(id) => !schemaTableIds.includes(id)
);
}
} else {
// Schema is not visible, make it visible
newSchemaIds = [
...new Set([...(currentSchemaIds || []), schemaId]),
];
// Add tables from this schema to tableIds if tableIds is defined
if (prev.tableIds) {
const schemaTableIds = allTables
.filter((table) => table.schemaId === schemaId)
.map((table) => table.id);
newTableIds = [
...new Set([
...prev.tableIds,
...schemaTableIds,
]),
];
}
}
// Use reduceFilter to optimize and handle edge cases
return reduceFilter(
{
schemaIds: newSchemaIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allSchemasIds, allTables, databaseType]
);
const toggleTableFilterForNoSchema = useCallback(
(tableId: string) => {
setFilter((prev) => {
const currentTableIds = prev.tableIds;
// Check if table is currently visible
const isTableVisible = filterTable({
table: { id: tableId, schema: undefined },
filter: prev,
options: { defaultSchema: undefined },
});
let newTableIds: string[] | undefined;
if (isTableVisible) {
// Table is visible, make it not visible
if (!currentTableIds) {
// All tables are visible, create filter with all except this one
newTableIds = allTables
.filter((t) => t.id !== tableId)
.map((t) => t.id);
} else {
// Remove this table from the filter
newTableIds = currentTableIds.filter(
(id) => id !== tableId
);
}
} else {
// Table is not visible, make it visible
newTableIds = [
...new Set([...(currentTableIds || []), tableId]),
];
}
// Use reduceFilter to optimize and handle edge cases
return reduceFilter(
{
schemaIds: undefined,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType]
);
const toggleTableFilter: DiagramFilterContext['toggleTableFilter'] =
useCallback(
(tableId: string) => {
if (!databasesWithSchemas.includes(databaseType)) {
// No schemas, toggle table filter without schema context
toggleTableFilterForNoSchema(tableId);
return;
}
setFilter((prev) => {
// Find the table in the tables list
const tableInfo = allTables.find((t) => t.id === tableId);
if (!tableInfo) {
return prev;
}
// Check if table is currently visible using filterTable
const isTableVisible = filterTable({
table: {
id: tableInfo.id,
schema: tableInfo.schema,
},
filter: prev,
options: {
defaultSchema: defaultSchemas[databaseType],
},
});
let newSchemaIds = prev.schemaIds;
let newTableIds = prev.tableIds;
if (isTableVisible) {
// Table is visible, make it not visible
// If the table is visible due to its schema being in schemaIds
if (
tableInfo?.schemaId &&
prev.schemaIds?.includes(tableInfo.schemaId)
) {
// Remove the schema from schemaIds and add all other tables from that schema to tableIds
newSchemaIds = prev.schemaIds.filter(
(id) => id !== tableInfo.schemaId
);
// Get all other tables from this schema (except the one being toggled)
const otherTablesFromSchema = allTables
.filter(
(t) =>
t.schemaId === tableInfo.schemaId &&
t.id !== tableId
)
.map((t) => t.id);
// Add these tables to tableIds
newTableIds = [
...(prev.tableIds || []),
...otherTablesFromSchema,
];
} else if (prev.tableIds?.includes(tableId)) {
// Table is visible because it's in tableIds, remove it
newTableIds = prev.tableIds.filter(
(id) => id !== tableId
);
} else if (!prev.tableIds && !prev.schemaIds) {
// No filters = all visible, create filter with all tables except this one
newTableIds = allTables
.filter((t) => t.id !== tableId)
.map((t) => t.id);
}
} else {
// Table is not visible, make it visible by adding to tableIds
newTableIds = [...(prev.tableIds || []), tableId];
}
// Use reduceFilter to optimize and handle edge cases
return reduceFilter(
{
schemaIds: newSchemaIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType, toggleTableFilterForNoSchema]
);
const addSchemaToFilter: DiagramFilterContext['addSchemaToFilter'] =
useCallback(
(schemaId: string) => {
setFilter((prev) => {
const currentSchemaIds = prev.schemaIds;
if (!currentSchemaIds) {
// No schemas are filtered
return prev;
}
// If schema is already filtered, do nothing
if (currentSchemaIds.includes(schemaId)) {
return prev;
}
// Add schema to the filter
const newSchemaIds = [...currentSchemaIds, schemaId];
if (newSchemaIds.length === allSchemasIds.length) {
// All schemas are now filtered, set to undefined
return {
...prev,
schemaIds: undefined,
} satisfies DiagramFilter;
}
return {
...prev,
schemaIds: newSchemaIds,
} satisfies DiagramFilter;
});
},
[allSchemasIds.length]
);
const hasActiveFilter: boolean = useMemo(() => {
return !!filter.schemaIds || !!filter.tableIds;
}, [filter]);
const schemasDisplayed: DiagramFilterContext['schemasDisplayed'] =
useMemo(() => {
if (!hasActiveFilter) {
return schemas;
}
const displayedSchemaIds = new Set<string>();
for (const table of allTables) {
if (
filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[databaseType],
},
})
) {
if (table.schemaId) {
displayedSchemaIds.add(table.schemaId);
}
}
}
return schemas.filter((schema) =>
displayedSchemaIds.has(schema.id)
);
}, [hasActiveFilter, schemas, allTables, filter, databaseType]);
const addTablesToFilter: DiagramFilterContext['addTablesToFilter'] =
useCallback(
({ tableIds, filterCallback }) => {
setFilter((prev) => {
let tableIdsToAdd: string[];
if (tableIds) {
// If tableIds are provided, use them directly
tableIdsToAdd = tableIds;
} else if (filterCallback) {
// If filterCallback is provided, filter tables based on it
tableIdsToAdd = allTables
.filter(filterCallback)
.map((table) => table.id);
} else {
// If neither is provided, do nothing
return prev;
}
const filterByTableIds = spreadFilterTables(
prev,
allTables satisfies FilterTableInfo[]
);
const currentTableIds = filterByTableIds.tableIds || [];
const newTableIds = [
...new Set([...currentTableIds, ...tableIdsToAdd]),
];
return reduceFilter(
{
...filterByTableIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType]
);
const removeTablesFromFilter: DiagramFilterContext['removeTablesFromFilter'] =
useCallback(
({ tableIds, filterCallback }) => {
setFilter((prev) => {
let tableIdsToRemovoe: string[];
if (tableIds) {
// If tableIds are provided, use them directly
tableIdsToRemovoe = tableIds;
} else if (filterCallback) {
// If filterCallback is provided, filter tables based on it
tableIdsToRemovoe = allTables
.filter(filterCallback)
.map((table) => table.id);
} else {
// If neither is provided, do nothing
return prev;
}
const filterByTableIds = spreadFilterTables(
prev,
allTables satisfies FilterTableInfo[]
);
const currentTableIds = filterByTableIds.tableIds || [];
const newTableIds = currentTableIds.filter(
(id) => !tableIdsToRemovoe.includes(id)
);
return reduceFilter(
{
...filterByTableIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType]
);
const eventConsumer = useCallback(
(event: ChartDBEvent) => {
if (!hasActiveFilter) {
return;
}
if (event.action === 'add_tables') {
addTablesToFilter({
tableIds: event.data.tables.map((table) => table.id),
});
}
},
[hasActiveFilter, addTablesToFilter]
);
events.useSubscription(eventConsumer);
const value: DiagramFilterContext = {
loading,
filter,
clearSchemaIdsFilter: clearSchemaIds,
setTableIdsFilterEmpty: setTableIdsEmpty,
clearTableIdsFilter: clearTableIds,
resetFilter,
toggleSchemaFilter,
toggleTableFilter,
addSchemaToFilter,
hasActiveFilter,
schemasDisplayed,
addTablesToFilter,
removeTablesFromFilter,
};
return (
<diagramFilterContext.Provider value={value}>
{children}
</diagramFilterContext.Provider>
);
};

View File

@@ -0,0 +1,4 @@
import { useContext } from 'react';
import { diagramFilterContext } from './diagram-filter-context';
export const useDiagramFilter = () => useContext(diagramFilterContext);

View File

@@ -7,7 +7,6 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-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 { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog'; import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
import type { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog'; import type { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
import type { CreateDiagramDialogProps } from '@/dialogs/create-diagram-dialog/create-diagram-dialog'; import type { CreateDiagramDialogProps } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
@@ -67,12 +66,6 @@ export interface DialogContext {
params: Omit<ImportDiagramDialogProps, 'dialog'> params: Omit<ImportDiagramDialogProps, 'dialog'>
) => void; ) => void;
closeImportDiagramDialog: () => void; closeImportDiagramDialog: () => void;
// Import DBML dialog
openImportDBMLDialog: (
params?: Omit<ImportDBMLDialogProps, 'dialog'>
) => void;
closeImportDBMLDialog: () => void;
} }
export const dialogContext = createContext<DialogContext>({ export const dialogContext = createContext<DialogContext>({
@@ -96,6 +89,4 @@ export const dialogContext = createContext<DialogContext>({
closeExportDiagramDialog: emptyFn, closeExportDiagramDialog: emptyFn,
openImportDiagramDialog: emptyFn, openImportDiagramDialog: emptyFn,
closeImportDiagramDialog: emptyFn, closeImportDiagramDialog: emptyFn,
openImportDBMLDialog: emptyFn,
closeImportDBMLDialog: emptyFn,
}); });

View File

@@ -20,8 +20,6 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog'; import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog'; import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog'; import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
import { ImportDBMLDialog } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
export const DialogProvider: React.FC<React.PropsWithChildren> = ({ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
children, children,
@@ -132,11 +130,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openImportDiagramDialog, setOpenImportDiagramDialog] = const [openImportDiagramDialog, setOpenImportDiagramDialog] =
useState(false); useState(false);
// Import DBML dialog
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
const [importDBMLDialogParams, setImportDBMLDialogParams] =
useState<Omit<ImportDBMLDialogProps, 'dialog'>>();
return ( return (
<dialogContext.Provider <dialogContext.Provider
value={{ value={{
@@ -165,11 +158,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
openImportDiagramDialog: () => setOpenImportDiagramDialog(true), openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
closeImportDiagramDialog: () => closeImportDiagramDialog: () =>
setOpenImportDiagramDialog(false), setOpenImportDiagramDialog(false),
openImportDBMLDialog: (params) => {
setImportDBMLDialogParams(params);
setOpenImportDBMLDialog(true);
},
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
}} }}
> >
{children} {children}
@@ -204,10 +192,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
/> />
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} /> <ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} /> <ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
<ImportDBMLDialog
dialog={{ open: openImportDBMLDialog }}
{...importDBMLDialogParams}
/>
</dialogContext.Provider> </dialogContext.Provider>
); );
}; };

View File

@@ -15,9 +15,9 @@ export type DiffEventBase<T extends DiffEventType, D> = {
}; };
export type DiffCalculatedData = { export type DiffCalculatedData = {
tablesAdded: DBTable[]; tablesToAdd: DBTable[];
fieldsAdded: Map<string, DBField[]>; fieldsToAdd: Map<string, DBField[]>;
relationshipsAdded: DBRelationship[]; relationshipsToAdd: DBRelationship[];
}; };
export type DiffCalculatedEvent = DiffEventBase< export type DiffCalculatedEvent = DiffEventBase<
@@ -44,15 +44,21 @@ export interface DiffContext {
options?: { options?: {
summaryOnly?: boolean; summaryOnly?: boolean;
}; };
}) => void; }) => { foundDiff: boolean };
resetDiff: () => void; resetDiff: () => void;
// table diff // table diff
checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean; checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean;
checkIfNewTable: ({ tableId }: { tableId: string }) => boolean; checkIfNewTable: ({ tableId }: { tableId: string }) => boolean;
checkIfTableRemoved: ({ tableId }: { tableId: string }) => boolean; checkIfTableRemoved: ({ tableId }: { tableId: string }) => boolean;
getTableNewName: ({ tableId }: { tableId: string }) => string | null; getTableNewName: ({ tableId }: { tableId: string }) => {
getTableNewColor: ({ tableId }: { tableId: string }) => string | null; old: string;
new: string;
} | null;
getTableNewColor: ({ tableId }: { tableId: string }) => {
old: string;
new: string;
} | null;
// field diff // field diff
checkIfFieldHasChange: ({ checkIfFieldHasChange: ({
@@ -64,17 +70,46 @@ export interface DiffContext {
}) => boolean; }) => boolean;
checkIfFieldRemoved: ({ fieldId }: { fieldId: string }) => boolean; checkIfFieldRemoved: ({ fieldId }: { fieldId: string }) => boolean;
checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean; checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean;
getFieldNewName: ({ fieldId }: { fieldId: string }) => string | null; getFieldNewName: ({
getFieldNewType: ({ fieldId }: { fieldId: string }) => DataType | null; fieldId,
getFieldNewPrimaryKey: ({ fieldId }: { fieldId: string }) => boolean | null; }: {
getFieldNewNullable: ({ fieldId }: { fieldId: string }) => boolean | null; fieldId: string;
}) => { old: string; new: string } | null;
getFieldNewType: ({
fieldId,
}: {
fieldId: string;
}) => { old: DataType; new: DataType } | null;
getFieldNewPrimaryKey: ({
fieldId,
}: {
fieldId: string;
}) => { old: boolean; new: boolean } | null;
getFieldNewNullable: ({
fieldId,
}: {
fieldId: string;
}) => { old: boolean; new: boolean } | null;
getFieldNewCharacterMaximumLength: ({ getFieldNewCharacterMaximumLength: ({
fieldId, fieldId,
}: { }: {
fieldId: string; fieldId: string;
}) => string | null; }) => { old: string; new: string } | null;
getFieldNewScale: ({ fieldId }: { fieldId: string }) => number | null; getFieldNewScale: ({
getFieldNewPrecision: ({ fieldId }: { fieldId: string }) => number | null; fieldId,
}: {
fieldId: string;
}) => { old: number; new: number } | null;
getFieldNewPrecision: ({
fieldId,
}: {
fieldId: string;
}) => { old: number; new: number } | null;
getFieldNewIsArray: ({
fieldId,
}: {
fieldId: string;
}) => { old: boolean; new: boolean } | null;
// relationship diff // relationship diff
checkIfNewRelationship: ({ checkIfNewRelationship: ({

View File

@@ -36,7 +36,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const events = useEventEmitter<DiffEvent>(); const events = useEventEmitter<DiffEvent>();
const generateNewFieldsMap = useCallback( const generateFieldsToAddMap = useCallback(
({ ({
diffMap, diffMap,
newDiagram, newDiagram,
@@ -66,7 +66,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
[] []
); );
const findNewRelationships = useCallback( const findRelationshipsToAdd = useCallback(
({ ({
diffMap, diffMap,
newDiagram, newDiagram,
@@ -101,7 +101,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
diffMap: DiffMap; diffMap: DiffMap;
}): DiffCalculatedData => { }): DiffCalculatedData => {
return { return {
tablesAdded: tablesToAdd:
newDiagram?.tables?.filter((table) => { newDiagram?.tables?.filter((table) => {
const tableKey = getDiffMapKey({ const tableKey = getDiffMapKey({
diffObject: 'table', diffObject: 'table',
@@ -114,17 +114,17 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
); );
}) ?? [], }) ?? [],
fieldsAdded: generateNewFieldsMap({ fieldsToAdd: generateFieldsToAddMap({
diffMap: diffMap, diffMap: diffMap,
newDiagram: newDiagram, newDiagram: newDiagram,
}), }),
relationshipsAdded: findNewRelationships({ relationshipsToAdd: findRelationshipsToAdd({
diffMap: diffMap, diffMap: diffMap,
newDiagram: newDiagram, newDiagram: newDiagram,
}), }),
}; };
}, },
[findNewRelationships, generateNewFieldsMap] [findRelationshipsToAdd, generateFieldsToAddMap]
); );
const calculateDiff: DiffContext['calculateDiff'] = useCallback( const calculateDiff: DiffContext['calculateDiff'] = useCallback(
@@ -149,6 +149,8 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
newDiagram: newDiagramArg, newDiagram: newDiagramArg,
}), }),
}); });
return { foundDiff: !!newDiffs.size };
}, },
[setDiffMap, events, generateDiffCalculatedData] [setDiffMap, events, generateDiffCalculatedData]
); );
@@ -165,7 +167,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(tableNameKey); const diff = diffMap.get(tableNameKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as string; return {
new: diff.newValue as string,
old: diff.oldValue as string,
};
} }
} }
@@ -186,7 +191,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(tableColorKey); const diff = diffMap.get(tableColorKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as string; return {
new: diff.newValue as string,
old: diff.oldValue as string,
};
} }
} }
return null; return null;
@@ -277,7 +285,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey); const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as string; return {
old: diff.oldValue as string,
new: diff.newValue as string,
};
} }
} }
@@ -298,7 +309,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey); const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as DataType; return {
old: diff.oldValue as DataType,
new: diff.newValue as DataType,
};
} }
} }
@@ -321,7 +335,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey); const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as boolean; return {
old: diff.oldValue as boolean,
new: diff.newValue as boolean,
};
} }
} }
@@ -342,7 +359,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey); const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as boolean; return {
old: diff.oldValue as boolean,
new: diff.newValue as boolean,
};
} }
} }
@@ -365,7 +385,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey); const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as string; return {
old: diff.oldValue as string,
new: diff.newValue as string,
};
} }
} }
@@ -386,7 +409,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey); const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as number; return {
old: diff.oldValue as number,
new: diff.newValue as number,
};
} }
} }
@@ -409,7 +435,34 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey); const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') { if (diff?.type === 'changed') {
return diff.newValue as number; return {
old: diff.oldValue as number,
new: diff.newValue as number,
};
}
}
return null;
},
[diffMap]
);
const getFieldNewIsArray = useCallback<DiffContext['getFieldNewIsArray']>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'isArray',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return {
old: diff.oldValue as boolean,
new: diff.newValue as boolean,
};
} }
} }
@@ -491,6 +544,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
getFieldNewCharacterMaximumLength, getFieldNewCharacterMaximumLength,
getFieldNewScale, getFieldNewScale,
getFieldNewPrecision, getFieldNewPrecision,
getFieldNewIsArray,
// relationship diff // relationship diff
checkIfNewRelationship, checkIfNewRelationship,

View File

@@ -39,6 +39,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
addCustomTypes, addCustomTypes,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
addNotes,
removeNotes,
updateNote,
} = useChartDB(); } = useChartDB();
const redoActionHandlers = useMemo( const redoActionHandlers = useMemo(
@@ -135,6 +138,15 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
updateHistory: false, updateHistory: false,
}); });
}, },
addNotes: ({ redoData: { notes } }) => {
return addNotes(notes, { updateHistory: false });
},
removeNotes: ({ redoData: { noteIds } }) => {
return removeNotes(noteIds, { updateHistory: false });
},
updateNote: ({ redoData: { noteId, note } }) => {
return updateNote(noteId, note, { updateHistory: false });
},
}), }),
[ [
addTables, addTables,
@@ -160,6 +172,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
addCustomTypes, addCustomTypes,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
addNotes,
removeNotes,
updateNote,
] ]
); );
@@ -271,6 +286,15 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
updateHistory: false, updateHistory: false,
}); });
}, },
addNotes: ({ undoData: { noteIds } }) => {
return removeNotes(noteIds, { updateHistory: false });
},
removeNotes: ({ undoData: { notes } }) => {
return addNotes(notes, { updateHistory: false });
},
updateNote: ({ undoData: { noteId, note } }) => {
return updateNote(noteId, note, { updateHistory: false });
},
}), }),
[ [
addTables, addTables,
@@ -296,6 +320,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
addCustomTypes, addCustomTypes,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
addNotes,
removeNotes,
updateNote,
] ]
); );

View File

@@ -6,6 +6,7 @@ import type { DBRelationship } from '@/lib/domain/db-relationship';
import type { DBDependency } from '@/lib/domain/db-dependency'; import type { DBDependency } from '@/lib/domain/db-dependency';
import type { Area } from '@/lib/domain/area'; import type { Area } from '@/lib/domain/area';
import type { DBCustomType } from '@/lib/domain/db-custom-type'; import type { DBCustomType } from '@/lib/domain/db-custom-type';
import type { Note } from '@/lib/domain/note';
type Action = keyof ChartDBContext; type Action = keyof ChartDBContext;
@@ -161,6 +162,24 @@ type RedoUndoActionRemoveCustomTypes = RedoUndoActionBase<
{ customTypes: DBCustomType[] } { customTypes: DBCustomType[] }
>; >;
type RedoUndoActionAddNotes = RedoUndoActionBase<
'addNotes',
{ notes: Note[] },
{ noteIds: string[] }
>;
type RedoUndoActionUpdateNote = RedoUndoActionBase<
'updateNote',
{ noteId: string; note: Partial<Note> },
{ noteId: string; note: Partial<Note> }
>;
type RedoUndoActionRemoveNotes = RedoUndoActionBase<
'removeNotes',
{ noteIds: string[] },
{ notes: Note[] }
>;
export type RedoUndoAction = export type RedoUndoAction =
| RedoUndoActionAddTables | RedoUndoActionAddTables
| RedoUndoActionRemoveTables | RedoUndoActionRemoveTables
@@ -184,7 +203,10 @@ export type RedoUndoAction =
| RedoUndoActionRemoveAreas | RedoUndoActionRemoveAreas
| RedoUndoActionAddCustomTypes | RedoUndoActionAddCustomTypes
| RedoUndoActionUpdateCustomType | RedoUndoActionUpdateCustomType
| RedoUndoActionRemoveCustomTypes; | RedoUndoActionRemoveCustomTypes
| RedoUndoActionAddNotes
| RedoUndoActionUpdateNote
| RedoUndoActionRemoveNotes;
export type RedoActionData<T extends Action> = Extract< export type RedoActionData<T extends Action> = Extract<
RedoUndoAction, RedoUndoAction,

View File

@@ -2,29 +2,37 @@ import { emptyFn } from '@/lib/utils';
import { createContext } from 'react'; import { createContext } from 'react';
export type SidebarSection = export type SidebarSection =
| 'dbml'
| 'tables' | 'tables'
| 'relationships' | 'refs'
| 'dependencies' | 'customTypes'
| 'areas' | 'visuals';
| 'customTypes';
export type VisualsTab = 'areas' | 'notes';
export interface LayoutContext { export interface LayoutContext {
openedTableInSidebar: string | undefined; openedTableInSidebar: string | undefined;
openTableFromSidebar: (tableId: string) => void; openTableFromSidebar: (tableId: string) => void;
closeAllTablesInSidebar: () => void; closeAllTablesInSidebar: () => void;
openedRelationshipInSidebar: string | undefined;
openRelationshipFromSidebar: (relationshipId: string) => void; openRelationshipFromSidebar: (relationshipId: string) => void;
closeAllRelationshipsInSidebar: () => void; closeAllRelationshipsInSidebar: () => void;
openedDependencyInSidebar: string | undefined;
openDependencyFromSidebar: (dependencyId: string) => void; openDependencyFromSidebar: (dependencyId: string) => void;
closeAllDependenciesInSidebar: () => void; closeAllDependenciesInSidebar: () => void;
openedRefInSidebar: string | undefined;
openRefFromSidebar: (refId: string) => void;
closeAllRefsInSidebar: () => void;
openedAreaInSidebar: string | undefined; openedAreaInSidebar: string | undefined;
openAreaFromSidebar: (areaId: string) => void; openAreaFromSidebar: (areaId: string) => void;
closeAllAreasInSidebar: () => void; closeAllAreasInSidebar: () => void;
openedNoteInSidebar: string | undefined;
openNoteFromSidebar: (noteId: string) => void;
closeAllNotesInSidebar: () => void;
openedCustomTypeInSidebar: string | undefined; openedCustomTypeInSidebar: string | undefined;
openCustomTypeFromSidebar: (customTypeId: string) => void; openCustomTypeFromSidebar: (customTypeId: string) => void;
closeAllCustomTypesInSidebar: () => void; closeAllCustomTypesInSidebar: () => void;
@@ -32,32 +40,37 @@ export interface LayoutContext {
selectedSidebarSection: SidebarSection; selectedSidebarSection: SidebarSection;
selectSidebarSection: (section: SidebarSection) => void; selectSidebarSection: (section: SidebarSection) => void;
selectedVisualsTab: VisualsTab;
selectVisualsTab: (tab: VisualsTab) => void;
isSidePanelShowed: boolean; isSidePanelShowed: boolean;
hideSidePanel: () => void; hideSidePanel: () => void;
showSidePanel: () => void; showSidePanel: () => void;
toggleSidePanel: () => void; toggleSidePanel: () => void;
isSelectSchemaOpen: boolean;
openSelectSchema: () => void;
closeSelectSchema: () => void;
} }
export const layoutContext = createContext<LayoutContext>({ export const layoutContext = createContext<LayoutContext>({
openedTableInSidebar: undefined, openedTableInSidebar: undefined,
selectedSidebarSection: 'tables', selectedSidebarSection: 'tables',
openedRelationshipInSidebar: undefined,
openRelationshipFromSidebar: emptyFn, openRelationshipFromSidebar: emptyFn,
closeAllRelationshipsInSidebar: emptyFn, closeAllRelationshipsInSidebar: emptyFn,
openedDependencyInSidebar: undefined,
openDependencyFromSidebar: emptyFn, openDependencyFromSidebar: emptyFn,
closeAllDependenciesInSidebar: emptyFn, closeAllDependenciesInSidebar: emptyFn,
openedRefInSidebar: undefined,
openRefFromSidebar: emptyFn,
closeAllRefsInSidebar: emptyFn,
openedAreaInSidebar: undefined, openedAreaInSidebar: undefined,
openAreaFromSidebar: emptyFn, openAreaFromSidebar: emptyFn,
closeAllAreasInSidebar: emptyFn, closeAllAreasInSidebar: emptyFn,
openedNoteInSidebar: undefined,
openNoteFromSidebar: emptyFn,
closeAllNotesInSidebar: emptyFn,
openedCustomTypeInSidebar: undefined, openedCustomTypeInSidebar: undefined,
openCustomTypeFromSidebar: emptyFn, openCustomTypeFromSidebar: emptyFn,
closeAllCustomTypesInSidebar: emptyFn, closeAllCustomTypesInSidebar: emptyFn,
@@ -66,12 +79,11 @@ export const layoutContext = createContext<LayoutContext>({
openTableFromSidebar: emptyFn, openTableFromSidebar: emptyFn,
closeAllTablesInSidebar: emptyFn, closeAllTablesInSidebar: emptyFn,
selectedVisualsTab: 'areas',
selectVisualsTab: emptyFn,
isSidePanelShowed: false, isSidePanelShowed: false,
hideSidePanel: emptyFn, hideSidePanel: emptyFn,
showSidePanel: emptyFn, showSidePanel: emptyFn,
toggleSidePanel: emptyFn, toggleSidePanel: emptyFn,
isSelectSchemaOpen: false,
openSelectSchema: emptyFn,
closeSelectSchema: emptyFn,
}); });

View File

@@ -1,5 +1,9 @@
import React from 'react'; import React from 'react';
import type { LayoutContext, SidebarSection } from './layout-context'; import type {
LayoutContext,
SidebarSection,
VisualsTab,
} from './layout-context';
import { layoutContext } from './layout-context'; import { layoutContext } from './layout-context';
import { useBreakpoint } from '@/hooks/use-breakpoint'; import { useBreakpoint } from '@/hooks/use-breakpoint';
@@ -10,34 +14,42 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState< const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState<
string | undefined string | undefined
>(); >();
const [openedRelationshipInSidebar, setOpenedRelationshipInSidebar] = const [openedRefInSidebar, setOpenedRefInSidebar] = React.useState<
React.useState<string | undefined>(); string | undefined
const [openedDependencyInSidebar, setOpenedDependencyInSidebar] = >();
React.useState<string | undefined>();
const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState< const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState<
string | undefined string | undefined
>(); >();
const [openedNoteInSidebar, setOpenedNoteInSidebar] = React.useState<
string | undefined
>();
const [openedCustomTypeInSidebar, setOpenedCustomTypeInSidebar] = const [openedCustomTypeInSidebar, setOpenedCustomTypeInSidebar] =
React.useState<string | undefined>(); React.useState<string | undefined>();
const [selectedSidebarSection, setSelectedSidebarSection] = const [selectedSidebarSection, setSelectedSidebarSection] =
React.useState<SidebarSection>('tables'); React.useState<SidebarSection>('tables');
const [selectedVisualsTab, setSelectedVisualsTab] =
React.useState<VisualsTab>('areas');
const [isSidePanelShowed, setIsSidePanelShowed] = const [isSidePanelShowed, setIsSidePanelShowed] =
React.useState<boolean>(isDesktop); React.useState<boolean>(isDesktop);
const [isSelectSchemaOpen, setIsSelectSchemaOpen] =
React.useState<boolean>(false);
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] = const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
() => setOpenedTableInSidebar(''); () => setOpenedTableInSidebar('');
const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] = const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] =
() => setOpenedRelationshipInSidebar(''); () => setOpenedRefInSidebar('');
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] = const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
() => setOpenedDependencyInSidebar(''); () => setOpenedRefInSidebar('');
const closeAllRefsInSidebar: LayoutContext['closeAllRefsInSidebar'] = () =>
setOpenedRefInSidebar('');
const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] = const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] =
() => setOpenedAreaInSidebar(''); () => setOpenedAreaInSidebar('');
const closeAllNotesInSidebar: LayoutContext['closeAllNotesInSidebar'] =
() => setOpenedNoteInSidebar('');
const closeAllCustomTypesInSidebar: LayoutContext['closeAllCustomTypesInSidebar'] = const closeAllCustomTypesInSidebar: LayoutContext['closeAllCustomTypesInSidebar'] =
() => setOpenedCustomTypeInSidebar(''); () => setOpenedCustomTypeInSidebar('');
@@ -62,25 +74,41 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] = const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] =
(relationshipId) => { (relationshipId) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('relationships'); setSelectedSidebarSection('refs');
setOpenedRelationshipInSidebar(relationshipId); setOpenedRefInSidebar(relationshipId);
}; };
const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] = const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] =
(dependencyId) => { (dependencyId) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('dependencies'); setSelectedSidebarSection('refs');
setOpenedDependencyInSidebar(dependencyId); setOpenedRefInSidebar(dependencyId);
}; };
const openRefFromSidebar: LayoutContext['openRefFromSidebar'] = (refId) => {
showSidePanel();
setSelectedSidebarSection('refs');
setOpenedRefInSidebar(refId);
};
const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = ( const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = (
areaId areaId
) => { ) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('areas'); setSelectedSidebarSection('visuals');
setSelectedVisualsTab('areas');
setOpenedAreaInSidebar(areaId); setOpenedAreaInSidebar(areaId);
}; };
const openNoteFromSidebar: LayoutContext['openNoteFromSidebar'] = (
noteId
) => {
showSidePanel();
setSelectedSidebarSection('visuals');
setSelectedVisualsTab('notes');
setOpenedNoteInSidebar(noteId);
};
const openCustomTypeFromSidebar: LayoutContext['openCustomTypeFromSidebar'] = const openCustomTypeFromSidebar: LayoutContext['openCustomTypeFromSidebar'] =
(customTypeId) => { (customTypeId) => {
showSidePanel(); showSidePanel();
@@ -88,11 +116,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
setOpenedTableInSidebar(customTypeId); setOpenedTableInSidebar(customTypeId);
}; };
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
setIsSelectSchemaOpen(true);
const closeSelectSchema: LayoutContext['closeSelectSchema'] = () =>
setIsSelectSchemaOpen(false);
return ( return (
<layoutContext.Provider <layoutContext.Provider
value={{ value={{
@@ -100,7 +123,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
selectedSidebarSection, selectedSidebarSection,
openTableFromSidebar, openTableFromSidebar,
selectSidebarSection: setSelectedSidebarSection, selectSidebarSection: setSelectedSidebarSection,
openedRelationshipInSidebar,
openRelationshipFromSidebar, openRelationshipFromSidebar,
closeAllTablesInSidebar, closeAllTablesInSidebar,
closeAllRelationshipsInSidebar, closeAllRelationshipsInSidebar,
@@ -108,18 +130,22 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
hideSidePanel, hideSidePanel,
showSidePanel, showSidePanel,
toggleSidePanel, toggleSidePanel,
isSelectSchemaOpen,
openSelectSchema,
closeSelectSchema,
openedDependencyInSidebar,
openDependencyFromSidebar, openDependencyFromSidebar,
closeAllDependenciesInSidebar, closeAllDependenciesInSidebar,
openedRefInSidebar,
openRefFromSidebar,
closeAllRefsInSidebar,
openedAreaInSidebar, openedAreaInSidebar,
openAreaFromSidebar, openAreaFromSidebar,
closeAllAreasInSidebar, closeAllAreasInSidebar,
openedNoteInSidebar,
openNoteFromSidebar,
closeAllNotesInSidebar,
openedCustomTypeInSidebar, openedCustomTypeInSidebar,
openCustomTypeFromSidebar, openCustomTypeFromSidebar,
closeAllCustomTypesInSidebar, closeAllCustomTypesInSidebar,
selectedVisualsTab,
selectVisualsTab: setSelectedVisualsTab,
}} }}
> >
{children} {children}

View File

@@ -4,8 +4,6 @@ import type { Theme } from '../theme-context/theme-context';
export type ScrollAction = 'pan' | 'zoom'; export type ScrollAction = 'pan' | 'zoom';
export type SchemasFilter = Record<string, string[]>;
export interface LocalConfigContext { export interface LocalConfigContext {
theme: Theme; theme: Theme;
setTheme: (theme: Theme) => void; setTheme: (theme: Theme) => void;
@@ -13,8 +11,8 @@ export interface LocalConfigContext {
scrollAction: ScrollAction; scrollAction: ScrollAction;
setScrollAction: (action: ScrollAction) => void; setScrollAction: (action: ScrollAction) => void;
schemasFilter: SchemasFilter; showDBViews: boolean;
setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>; setShowDBViews: (showViews: boolean) => void;
showCardinality: boolean; showCardinality: boolean;
setShowCardinality: (showCardinality: boolean) => void; setShowCardinality: (showCardinality: boolean) => void;
@@ -22,20 +20,12 @@ export interface LocalConfigContext {
showFieldAttributes: boolean; showFieldAttributes: boolean;
setShowFieldAttributes: (showFieldAttributes: boolean) => void; setShowFieldAttributes: (showFieldAttributes: boolean) => void;
hideMultiSchemaNotification: boolean;
setHideMultiSchemaNotification: (
hideMultiSchemaNotification: boolean
) => void;
githubRepoOpened: boolean; githubRepoOpened: boolean;
setGithubRepoOpened: (githubRepoOpened: boolean) => void; setGithubRepoOpened: (githubRepoOpened: boolean) => void;
starUsDialogLastOpen: number; starUsDialogLastOpen: number;
setStarUsDialogLastOpen: (lastOpen: number) => void; setStarUsDialogLastOpen: (lastOpen: number) => void;
showDependenciesOnCanvas: boolean;
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
showMiniMapOnCanvas: boolean; showMiniMapOnCanvas: boolean;
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void; setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
} }
@@ -47,8 +37,8 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
scrollAction: 'pan', scrollAction: 'pan',
setScrollAction: emptyFn, setScrollAction: emptyFn,
schemasFilter: {}, showDBViews: false,
setSchemasFilter: emptyFn, setShowDBViews: emptyFn,
showCardinality: true, showCardinality: true,
setShowCardinality: emptyFn, setShowCardinality: emptyFn,
@@ -56,18 +46,12 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
showFieldAttributes: true, showFieldAttributes: true,
setShowFieldAttributes: emptyFn, setShowFieldAttributes: emptyFn,
hideMultiSchemaNotification: false,
setHideMultiSchemaNotification: emptyFn,
githubRepoOpened: false, githubRepoOpened: false,
setGithubRepoOpened: emptyFn, setGithubRepoOpened: emptyFn,
starUsDialogLastOpen: 0, starUsDialogLastOpen: 0,
setStarUsDialogLastOpen: emptyFn, setStarUsDialogLastOpen: emptyFn,
showDependenciesOnCanvas: false,
setShowDependenciesOnCanvas: emptyFn,
showMiniMapOnCanvas: false, showMiniMapOnCanvas: false,
setShowMiniMapOnCanvas: emptyFn, setShowMiniMapOnCanvas: emptyFn,
}); });

View File

@@ -1,18 +1,16 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import type { SchemasFilter, ScrollAction } from './local-config-context'; import type { ScrollAction } from './local-config-context';
import { LocalConfigContext } from './local-config-context'; import { LocalConfigContext } from './local-config-context';
import type { Theme } from '../theme-context/theme-context'; import type { Theme } from '../theme-context/theme-context';
const themeKey = 'theme'; const themeKey = 'theme';
const scrollActionKey = 'scroll_action'; const scrollActionKey = 'scroll_action';
const schemasFilterKey = 'schemas_filter';
const showCardinalityKey = 'show_cardinality'; const showCardinalityKey = 'show_cardinality';
const showFieldAttributesKey = 'show_field_attributes'; const showFieldAttributesKey = 'show_field_attributes';
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
const githubRepoOpenedKey = 'github_repo_opened'; const githubRepoOpenedKey = 'github_repo_opened';
const starUsDialogLastOpenKey = 'star_us_dialog_last_open'; const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
const showMiniMapOnCanvasKey = 'show_minimap_on_canvas'; const showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
const showDBViewsKey = 'show_db_views';
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
children, children,
@@ -25,10 +23,8 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan' (localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
); );
const [schemasFilter, setSchemasFilter] = React.useState<SchemasFilter>( const [showDBViews, setShowDBViews] = React.useState<boolean>(
JSON.parse( (localStorage.getItem(showDBViewsKey) || 'false') === 'true'
localStorage.getItem(schemasFilterKey) || '{}'
) as SchemasFilter
); );
const [showCardinality, setShowCardinality] = React.useState<boolean>( const [showCardinality, setShowCardinality] = React.useState<boolean>(
@@ -40,12 +36,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
(localStorage.getItem(showFieldAttributesKey) || 'true') === 'true' (localStorage.getItem(showFieldAttributesKey) || 'true') === 'true'
); );
const [hideMultiSchemaNotification, setHideMultiSchemaNotification] =
React.useState<boolean>(
(localStorage.getItem(hideMultiSchemaNotificationKey) ||
'false') === 'true'
);
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>( const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
(localStorage.getItem(githubRepoOpenedKey) || 'false') === 'true' (localStorage.getItem(githubRepoOpenedKey) || 'false') === 'true'
); );
@@ -55,12 +45,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0') parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
); );
const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] =
React.useState<boolean>(
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
'true'
);
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] = const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
React.useState<boolean>( React.useState<boolean>(
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true' (localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
@@ -77,13 +61,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString()); localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
}, [githubRepoOpened]); }, [githubRepoOpened]);
useEffect(() => {
localStorage.setItem(
hideMultiSchemaNotificationKey,
hideMultiSchemaNotification.toString()
);
}, [hideMultiSchemaNotification]);
useEffect(() => { useEffect(() => {
localStorage.setItem(themeKey, theme); localStorage.setItem(themeKey, theme);
}, [theme]); }, [theme]);
@@ -93,20 +70,13 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
}, [scrollAction]); }, [scrollAction]);
useEffect(() => { useEffect(() => {
localStorage.setItem(schemasFilterKey, JSON.stringify(schemasFilter)); localStorage.setItem(showDBViewsKey, showDBViews.toString());
}, [schemasFilter]); }, [showDBViews]);
useEffect(() => { useEffect(() => {
localStorage.setItem(showCardinalityKey, showCardinality.toString()); localStorage.setItem(showCardinalityKey, showCardinality.toString());
}, [showCardinality]); }, [showCardinality]);
useEffect(() => {
localStorage.setItem(
showDependenciesOnCanvasKey,
showDependenciesOnCanvas.toString()
);
}, [showDependenciesOnCanvas]);
useEffect(() => { useEffect(() => {
localStorage.setItem( localStorage.setItem(
showMiniMapOnCanvasKey, showMiniMapOnCanvasKey,
@@ -121,20 +91,16 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
setTheme, setTheme,
scrollAction, scrollAction,
setScrollAction, setScrollAction,
schemasFilter, showDBViews,
setSchemasFilter, setShowDBViews,
showCardinality, showCardinality,
setShowCardinality, setShowCardinality,
showFieldAttributes, showFieldAttributes,
setShowFieldAttributes, setShowFieldAttributes,
hideMultiSchemaNotification,
setHideMultiSchemaNotification,
setGithubRepoOpened, setGithubRepoOpened,
githubRepoOpened, githubRepoOpened,
starUsDialogLastOpen, starUsDialogLastOpen,
setStarUsDialogLastOpen, setStarUsDialogLastOpen,
showDependenciesOnCanvas,
setShowDependenciesOnCanvas,
showMiniMapOnCanvas, showMiniMapOnCanvas,
setShowMiniMapOnCanvas, setShowMiniMapOnCanvas,
}} }}

View File

@@ -7,12 +7,22 @@ import type { ChartDBConfig } from '@/lib/domain/config';
import type { DBDependency } from '@/lib/domain/db-dependency'; import type { DBDependency } from '@/lib/domain/db-dependency';
import type { Area } from '@/lib/domain/area'; import type { Area } from '@/lib/domain/area';
import type { DBCustomType } from '@/lib/domain/db-custom-type'; import type { DBCustomType } from '@/lib/domain/db-custom-type';
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
import type { Note } from '@/lib/domain/note';
export interface StorageContext { export interface StorageContext {
// Config operations // Config operations
getConfig: () => Promise<ChartDBConfig | undefined>; getConfig: () => Promise<ChartDBConfig | undefined>;
updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>; updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>;
// Diagram filter operations
getDiagramFilter: (diagramId: string) => Promise<DiagramFilter | undefined>;
updateDiagramFilter: (
diagramId: string,
filter: DiagramFilter
) => Promise<void>;
deleteDiagramFilter: (diagramId: string) => Promise<void>;
// Diagram operations // Diagram operations
addDiagram: (params: { diagram: Diagram }) => Promise<void>; addDiagram: (params: { diagram: Diagram }) => Promise<void>;
listDiagrams: (options?: { listDiagrams: (options?: {
@@ -21,6 +31,7 @@ export interface StorageContext {
includeDependencies?: boolean; includeDependencies?: boolean;
includeAreas?: boolean; includeAreas?: boolean;
includeCustomTypes?: boolean; includeCustomTypes?: boolean;
includeNotes?: boolean;
}) => Promise<Diagram[]>; }) => Promise<Diagram[]>;
getDiagram: ( getDiagram: (
id: string, id: string,
@@ -30,6 +41,7 @@ export interface StorageContext {
includeDependencies?: boolean; includeDependencies?: boolean;
includeAreas?: boolean; includeAreas?: boolean;
includeCustomTypes?: boolean; includeCustomTypes?: boolean;
includeNotes?: boolean;
} }
) => Promise<Diagram | undefined>; ) => Promise<Diagram | undefined>;
updateDiagram: (params: { updateDiagram: (params: {
@@ -126,12 +138,30 @@ export interface StorageContext {
}) => Promise<void>; }) => Promise<void>;
listCustomTypes: (diagramId: string) => Promise<DBCustomType[]>; listCustomTypes: (diagramId: string) => Promise<DBCustomType[]>;
deleteDiagramCustomTypes: (diagramId: string) => Promise<void>; deleteDiagramCustomTypes: (diagramId: string) => Promise<void>;
// Note operations
addNote: (params: { diagramId: string; note: Note }) => Promise<void>;
getNote: (params: {
diagramId: string;
id: string;
}) => Promise<Note | undefined>;
updateNote: (params: {
id: string;
attributes: Partial<Note>;
}) => Promise<void>;
deleteNote: (params: { diagramId: string; id: string }) => Promise<void>;
listNotes: (diagramId: string) => Promise<Note[]>;
deleteDiagramNotes: (diagramId: string) => Promise<void>;
} }
export const storageInitialValue: StorageContext = { export const storageInitialValue: StorageContext = {
getConfig: emptyFn, getConfig: emptyFn,
updateConfig: emptyFn, updateConfig: emptyFn,
getDiagramFilter: emptyFn,
updateDiagramFilter: emptyFn,
deleteDiagramFilter: emptyFn,
addDiagram: emptyFn, addDiagram: emptyFn,
listDiagrams: emptyFn, listDiagrams: emptyFn,
getDiagram: emptyFn, getDiagram: emptyFn,
@@ -174,6 +204,14 @@ export const storageInitialValue: StorageContext = {
deleteCustomType: emptyFn, deleteCustomType: emptyFn,
listCustomTypes: emptyFn, listCustomTypes: emptyFn,
deleteDiagramCustomTypes: emptyFn, deleteDiagramCustomTypes: emptyFn,
// Note operations
addNote: emptyFn,
getNote: emptyFn,
updateNote: emptyFn,
deleteNote: emptyFn,
listNotes: emptyFn,
deleteDiagramNotes: emptyFn,
}; };
export const storageContext = export const storageContext =

View File

@@ -10,6 +10,8 @@ import type { ChartDBConfig } from '@/lib/domain/config';
import type { DBDependency } from '@/lib/domain/db-dependency'; import type { DBDependency } from '@/lib/domain/db-dependency';
import type { Area } from '@/lib/domain/area'; import type { Area } from '@/lib/domain/area';
import type { DBCustomType } from '@/lib/domain/db-custom-type'; import type { DBCustomType } from '@/lib/domain/db-custom-type';
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
import type { Note } from '@/lib/domain/note';
export const StorageProvider: React.FC<React.PropsWithChildren> = ({ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
children, children,
@@ -40,10 +42,18 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
DBCustomType & { diagramId: string }, DBCustomType & { diagramId: string },
'id' // primary key "id" (for the typings only) 'id' // primary key "id" (for the typings only)
>; >;
notes: EntityTable<
Note & { diagramId: string },
'id' // primary key "id" (for the typings only)
>;
config: EntityTable< config: EntityTable<
ChartDBConfig & { id: number }, ChartDBConfig & { id: number },
'id' // primary key "id" (for the typings only) 'id' // primary key "id" (for the typings only)
>; >;
diagram_filters: EntityTable<
DiagramFilter & { diagramId: string },
'diagramId' // primary key "id" (for the typings only)
>;
}; };
// Schema declaration: // Schema declaration:
@@ -190,6 +200,44 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
config: '++id, defaultDiagramId', config: '++id, defaultDiagramId',
}); });
dexieDB
.version(12)
.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',
areas: '++id, diagramId, name, x, y, width, height, color',
db_custom_types:
'++id, diagramId, schema, type, kind, values, fields',
config: '++id, defaultDiagramId',
diagram_filters: 'diagramId, tableIds, schemasIds',
})
.upgrade((tx) => {
tx.table('config').clear();
});
dexieDB.version(13).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',
areas: '++id, diagramId, name, x, y, width, height, color',
db_custom_types:
'++id, diagramId, schema, type, kind, values, fields',
config: '++id, defaultDiagramId',
diagram_filters: 'diagramId, tableIds, schemasIds',
notes: '++id, diagramId, content, x, y, width, height, color',
});
dexieDB.on('ready', async () => { dexieDB.on('ready', async () => {
const config = await dexieDB.config.get(1); const config = await dexieDB.config.get(1);
@@ -217,6 +265,34 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
[db] [db]
); );
const getDiagramFilter: StorageContext['getDiagramFilter'] = useCallback(
async (diagramId: string): Promise<DiagramFilter | undefined> => {
const filter = await db.diagram_filters.get({ diagramId });
return filter;
},
[db]
);
const updateDiagramFilter: StorageContext['updateDiagramFilter'] =
useCallback(
async (diagramId, filter): Promise<void> => {
await db.diagram_filters.put({
diagramId,
...filter,
});
},
[db]
);
const deleteDiagramFilter: StorageContext['deleteDiagramFilter'] =
useCallback(
async (diagramId: string): Promise<void> => {
await db.diagram_filters.where({ diagramId }).delete();
},
[db]
);
const addTable: StorageContext['addTable'] = useCallback( const addTable: StorageContext['addTable'] = useCallback(
async ({ diagramId, table }) => { async ({ diagramId, table }) => {
await db.db_tables.add({ await db.db_tables.add({
@@ -496,6 +572,56 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
[db] [db]
); );
// Note operations
const addNote: StorageContext['addNote'] = useCallback(
async ({ note, diagramId }) => {
await db.notes.add({
...note,
diagramId,
});
},
[db]
);
const getNote: StorageContext['getNote'] = useCallback(
async ({ diagramId, id }) => {
return await db.notes.get({ id, diagramId });
},
[db]
);
const updateNote: StorageContext['updateNote'] = useCallback(
async ({ id, attributes }) => {
await db.notes.update(id, attributes);
},
[db]
);
const deleteNote: StorageContext['deleteNote'] = useCallback(
async ({ diagramId, id }) => {
await db.notes.where({ id, diagramId }).delete();
},
[db]
);
const listNotes: StorageContext['listNotes'] = useCallback(
async (diagramId) => {
return await db.notes
.where('diagramId')
.equals(diagramId)
.toArray();
},
[db]
);
const deleteDiagramNotes: StorageContext['deleteDiagramNotes'] =
useCallback(
async (diagramId) => {
await db.notes.where('diagramId').equals(diagramId).delete();
},
[db]
);
const addDiagram: StorageContext['addDiagram'] = useCallback( const addDiagram: StorageContext['addDiagram'] = useCallback(
async ({ diagram }) => { async ({ diagram }) => {
const promises = []; const promises = [];
@@ -543,9 +669,22 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
) )
); );
const notes = diagram.notes ?? [];
promises.push(
...notes.map((note) => addNote({ diagramId: diagram.id, note }))
);
await Promise.all(promises); await Promise.all(promises);
}, },
[db, addArea, addCustomType, addDependency, addRelationship, addTable] [
db,
addArea,
addCustomType,
addDependency,
addRelationship,
addTable,
addNote,
]
); );
const listDiagrams: StorageContext['listDiagrams'] = useCallback( const listDiagrams: StorageContext['listDiagrams'] = useCallback(
@@ -556,6 +695,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
includeDependencies: false, includeDependencies: false,
includeAreas: false, includeAreas: false,
includeCustomTypes: false, includeCustomTypes: false,
includeNotes: false,
} }
): Promise<Diagram[]> => { ): Promise<Diagram[]> => {
let diagrams = await db.diagrams.toArray(); let diagrams = await db.diagrams.toArray();
@@ -609,6 +749,15 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
); );
} }
if (options.includeNotes) {
diagrams = await Promise.all(
diagrams.map(async (diagram) => {
diagram.notes = await listNotes(diagram.id);
return diagram;
})
);
}
return diagrams; return diagrams;
}, },
[ [
@@ -618,6 +767,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
listDependencies, listDependencies,
listRelationships, listRelationships,
listTables, listTables,
listNotes,
] ]
); );
@@ -630,6 +780,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
includeDependencies: false, includeDependencies: false,
includeAreas: false, includeAreas: false,
includeCustomTypes: false, includeCustomTypes: false,
includeNotes: false,
} }
): Promise<Diagram | undefined> => { ): Promise<Diagram | undefined> => {
const diagram = await db.diagrams.get(id); const diagram = await db.diagrams.get(id);
@@ -658,6 +809,10 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
diagram.customTypes = await listCustomTypes(id); diagram.customTypes = await listCustomTypes(id);
} }
if (options.includeNotes) {
diagram.notes = await listNotes(id);
}
return diagram; return diagram;
}, },
[ [
@@ -667,6 +822,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
listDependencies, listDependencies,
listRelationships, listRelationships,
listTables, listTables,
listNotes,
] ]
); );
@@ -695,6 +851,9 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
.where('diagramId') .where('diagramId')
.equals(id) .equals(id)
.modify({ diagramId: attributes.id }), .modify({ diagramId: attributes.id }),
db.notes.where('diagramId').equals(id).modify({
diagramId: attributes.id,
}),
]); ]);
} }
}, },
@@ -710,6 +869,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
db.db_dependencies.where('diagramId').equals(id).delete(), db.db_dependencies.where('diagramId').equals(id).delete(),
db.areas.where('diagramId').equals(id).delete(), db.areas.where('diagramId').equals(id).delete(),
db.db_custom_types.where('diagramId').equals(id).delete(), db.db_custom_types.where('diagramId').equals(id).delete(),
db.notes.where('diagramId').equals(id).delete(),
]); ]);
}, },
[db] [db]
@@ -756,6 +916,15 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
deleteCustomType, deleteCustomType,
listCustomTypes, listCustomTypes,
deleteDiagramCustomTypes, deleteDiagramCustomTypes,
addNote,
getNote,
updateNote,
deleteNote,
listNotes,
deleteDiagramNotes,
getDiagramFilter,
updateDiagramFilter,
deleteDiagramFilter,
}} }}
> >
{children} {children}

View File

@@ -42,6 +42,14 @@ import {
type ValidationResult, type ValidationResult,
} from '@/lib/data/sql-import/sql-validator'; } from '@/lib/data/sql-import/sql-validator';
import { SQLValidationStatus } from './sql-validation-status'; import { SQLValidationStatus } from './sql-validation-status';
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
import type { ImportMethod } from '@/lib/import-method/import-method';
import { detectImportMethod } from '@/lib/import-method/detect-import-method';
import { verifyDBML } from '@/lib/dbml/dbml-import/verify-dbml';
import {
clearErrorHighlight,
highlightErrorLine,
} from '@/components/code-snippet/dbml/utils';
const calculateContentSizeMB = (content: string): number => { const calculateContentSizeMB = (content: string): number => {
return content.length / (1024 * 1024); // Convert to MB return content.length / (1024 * 1024); // Convert to MB
@@ -55,49 +63,6 @@ const calculateIsLargeFile = (content: string): boolean => {
const errorScriptOutputMessage = const errorScriptOutputMessage =
'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.'; 'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.';
// Helper to detect if content is likely SQL DDL or JSON
const detectContentType = (content: string): 'query' | 'ddl' | null => {
if (!content || content.trim().length === 0) return null;
// Common SQL DDL keywords
const ddlKeywords = [
'CREATE TABLE',
'ALTER TABLE',
'DROP TABLE',
'CREATE INDEX',
'CREATE VIEW',
'CREATE PROCEDURE',
'CREATE FUNCTION',
'CREATE SCHEMA',
'CREATE DATABASE',
];
const upperContent = content.toUpperCase();
// Check for SQL DDL patterns
const hasDDLKeywords = ddlKeywords.some((keyword) =>
upperContent.includes(keyword)
);
if (hasDDLKeywords) return 'ddl';
// Check if it looks like JSON
try {
// Just check structure, don't need full parse for detection
if (
(content.trim().startsWith('{') && content.trim().endsWith('}')) ||
(content.trim().startsWith('[') && content.trim().endsWith(']'))
) {
return 'query';
}
} catch (error) {
// Not valid JSON, might be partial
console.error('Error detecting content type:', error);
}
// If we can't confidently detect, return null
return null;
};
export interface ImportDatabaseProps { export interface ImportDatabaseProps {
goBack?: () => void; goBack?: () => void;
onImport: () => void; onImport: () => void;
@@ -111,8 +76,8 @@ export interface ImportDatabaseProps {
>; >;
keepDialogAfterImport?: boolean; keepDialogAfterImport?: boolean;
title: string; title: string;
importMethod: 'query' | 'ddl'; importMethod: ImportMethod;
setImportMethod: (method: 'query' | 'ddl') => void; setImportMethod: (method: ImportMethod) => void;
} }
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
@@ -132,6 +97,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const { effectiveTheme } = useTheme(); const { effectiveTheme } = useTheme();
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null); const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const decorationsCollection = useRef<editor.IEditorDecorationsCollection>();
const pasteDisposableRef = useRef<IDisposable | null>(null); const pasteDisposableRef = useRef<IDisposable | null>(null);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -146,15 +112,20 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const [isAutoFixing, setIsAutoFixing] = useState(false); const [isAutoFixing, setIsAutoFixing] = useState(false);
const [showAutoFixButton, setShowAutoFixButton] = useState(false); const [showAutoFixButton, setShowAutoFixButton] = useState(false);
const clearDecorations = useCallback(() => {
clearErrorHighlight(decorationsCollection.current);
}, []);
useEffect(() => { useEffect(() => {
setScriptResult(''); setScriptResult('');
setErrorMessage(''); setErrorMessage('');
setShowCheckJsonButton(false); setShowCheckJsonButton(false);
}, [importMethod, setScriptResult]); }, [importMethod, setScriptResult]);
// Check if the ddl is valid // Check if the ddl or dbml is valid
useEffect(() => { useEffect(() => {
if (importMethod !== 'ddl') { clearDecorations();
if (importMethod === 'query') {
setSqlValidation(null); setSqlValidation(null);
setShowAutoFixButton(false); setShowAutoFixButton(false);
return; return;
@@ -163,9 +134,54 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
if (!scriptResult.trim()) { if (!scriptResult.trim()) {
setSqlValidation(null); setSqlValidation(null);
setShowAutoFixButton(false); setShowAutoFixButton(false);
setErrorMessage('');
return; return;
} }
if (importMethod === 'dbml') {
// Validate DBML by parsing it
const validateResponse = verifyDBML(scriptResult, { databaseType });
if (!validateResponse.hasError) {
setErrorMessage('');
setSqlValidation({
isValid: true,
errors: [],
warnings: [],
});
} else {
let errorMsg = 'Invalid DBML syntax';
let line: number = 1;
if (validateResponse.parsedError) {
errorMsg = validateResponse.parsedError.message;
line = validateResponse.parsedError.line;
highlightErrorLine({
error: validateResponse.parsedError,
model: editorRef.current?.getModel(),
editorDecorationsCollection:
decorationsCollection.current,
});
}
setSqlValidation({
isValid: false,
errors: [
{
message: errorMsg,
line: line,
type: 'syntax' as const,
},
],
warnings: [],
});
setErrorMessage(errorMsg);
}
setShowAutoFixButton(false);
return;
}
// SQL validation
// First run our validation based on database type // First run our validation based on database type
const validation = validateSQL(scriptResult, databaseType); const validation = validateSQL(scriptResult, databaseType);
setSqlValidation(validation); setSqlValidation(validation);
@@ -192,7 +208,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
setErrorMessage(result.error); setErrorMessage(result.error);
} }
}); });
}, [importMethod, scriptResult, databaseType]); }, [importMethod, scriptResult, databaseType, clearDecorations]);
// Check if the script result is a valid JSON // Check if the script result is a valid JSON
useEffect(() => { useEffect(() => {
@@ -320,6 +336,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const handleEditorDidMount = useCallback( const handleEditorDidMount = useCallback(
(editor: editor.IStandaloneCodeEditor) => { (editor: editor.IStandaloneCodeEditor) => {
editorRef.current = editor; editorRef.current = editor;
decorationsCollection.current =
editor.createDecorationsCollection();
// Cleanup previous disposable if it exists // Cleanup previous disposable if it exists
if (pasteDisposableRef.current) { if (pasteDisposableRef.current) {
@@ -338,7 +356,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const isLargeFile = calculateIsLargeFile(content); const isLargeFile = calculateIsLargeFile(content);
// First, detect content type to determine if we should switch modes // First, detect content type to determine if we should switch modes
const detectedType = detectContentType(content); const detectedType = detectImportMethod(content);
if (detectedType && detectedType !== importMethod) { if (detectedType && detectedType !== importMethod) {
// Switch to the detected mode immediately // Switch to the detected mode immediately
setImportMethod(detectedType); setImportMethod(detectedType);
@@ -352,7 +370,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
?.run(); ?.run();
}, 100); }, 100);
} }
// For DDL mode, do NOT format as it can break the SQL // For DDL and DBML modes, do NOT format as it can break the syntax
} else { } else {
// Content type didn't change, apply formatting based on current mode // Content type didn't change, apply formatting based on current mode
if (importMethod === 'query' && !isLargeFile) { if (importMethod === 'query' && !isLargeFile) {
@@ -363,7 +381,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
?.run(); ?.run();
}, 100); }, 100);
} }
// For DDL mode or large files, do NOT format // For DDL and DBML modes or large files, do NOT format
} }
}); });
@@ -410,16 +428,25 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
<div className="w-full text-center text-xs text-muted-foreground"> <div className="w-full text-center text-xs text-muted-foreground">
{importMethod === 'query' {importMethod === 'query'
? 'Smart Query Output' ? 'Smart Query Output'
: 'SQL Script'} : importMethod === 'dbml'
? 'DBML Script'
: 'SQL Script'}
</div> </div>
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
<Editor <Editor
value={scriptResult} value={scriptResult}
onChange={debouncedHandleInputChange} onChange={debouncedHandleInputChange}
language={importMethod === 'query' ? 'json' : 'sql'} language={
importMethod === 'query'
? 'json'
: importMethod === 'dbml'
? 'dbml'
: 'sql'
}
loading={<Spinner />} loading={<Spinner />}
onMount={handleEditorDidMount} onMount={handleEditorDidMount}
beforeMount={setupDBMLLanguage}
theme={ theme={
effectiveTheme === 'dark' effectiveTheme === 'dark'
? 'dbml-dark' ? 'dbml-dark'
@@ -430,7 +457,6 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
minimap: { enabled: false }, minimap: { enabled: false },
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
automaticLayout: true, automaticLayout: true,
glyphMargin: false,
lineNumbers: 'on', lineNumbers: 'on',
guides: { guides: {
indentation: false, indentation: false,
@@ -455,7 +481,9 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
</Suspense> </Suspense>
</div> </div>
{errorMessage || (importMethod === 'ddl' && sqlValidation) ? ( {errorMessage ||
((importMethod === 'ddl' || importMethod === 'dbml') &&
sqlValidation) ? (
<SQLValidationStatus <SQLValidationStatus
validation={sqlValidation} validation={sqlValidation}
errorMessage={errorMessage} errorMessage={errorMessage}

View File

@@ -15,9 +15,11 @@ import {
AvatarImage, AvatarImage,
} from '@/components/avatar/avatar'; } from '@/components/avatar/avatar';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Code } from 'lucide-react'; import { Code, FileCode } from 'lucide-react';
import { SmartQueryInstructions } from './instructions/smart-query-instructions'; import { SmartQueryInstructions } from './instructions/smart-query-instructions';
import { DDLInstructions } from './instructions/ddl-instructions'; import { DDLInstructions } from './instructions/ddl-instructions';
import { DBMLInstructions } from './instructions/dbml-instructions';
import type { ImportMethod } from '@/lib/import-method/import-method';
const DatabasesWithoutDDLInstructions: DatabaseType[] = [ const DatabasesWithoutDDLInstructions: DatabaseType[] = [
DatabaseType.CLICKHOUSE, DatabaseType.CLICKHOUSE,
@@ -30,8 +32,8 @@ export interface InstructionsSectionProps {
setDatabaseEdition: React.Dispatch< setDatabaseEdition: React.Dispatch<
React.SetStateAction<DatabaseEdition | undefined> React.SetStateAction<DatabaseEdition | undefined>
>; >;
importMethod: 'query' | 'ddl'; importMethod: ImportMethod;
setImportMethod: (method: 'query' | 'ddl') => void; setImportMethod: (method: ImportMethod) => void;
showSSMSInfoDialog: boolean; showSSMSInfoDialog: boolean;
setShowSSMSInfoDialog: (show: boolean) => void; setShowSSMSInfoDialog: (show: boolean) => void;
} }
@@ -115,48 +117,60 @@ export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
</div> </div>
) : null} ) : null}
{DatabasesWithoutDDLInstructions.includes(databaseType) ? null : ( <div className="flex flex-col gap-1">
<div className="flex flex-col gap-1"> <p className="text-sm leading-6 text-primary">
<p className="text-sm leading-6 text-primary"> How would you like to import?
How would you like to import? </p>
</p> <ToggleGroup
<ToggleGroup type="single"
type="single" className="ml-1 flex-wrap justify-start gap-2"
className="ml-1 flex-wrap justify-start gap-2" value={importMethod}
value={importMethod} onValueChange={(value) => {
onValueChange={(value) => { let selectedImportMethod: ImportMethod = 'query';
let selectedImportMethod: 'query' | 'ddl' = 'query'; if (value) {
if (value) { selectedImportMethod = value as ImportMethod;
selectedImportMethod = value as 'query' | 'ddl'; }
}
setImportMethod(selectedImportMethod); setImportMethod(selectedImportMethod);
}} }}
>
<ToggleGroupItem
value="query"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
> >
<ToggleGroupItem <Avatar className="h-3 w-4 rounded-none">
value="query" <AvatarImage src={logo} alt="query" />
variant="outline" <AvatarFallback>Query</AvatarFallback>
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700" </Avatar>
> Smart Query
<Avatar className="h-3 w-4 rounded-none"> </ToggleGroupItem>
<AvatarImage src={logo} alt="query" /> {!DatabasesWithoutDDLInstructions.includes(
<AvatarFallback>Query</AvatarFallback> databaseType
</Avatar> ) && (
Smart Query
</ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
value="ddl" value="ddl"
variant="outline" variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700" className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
> >
<Avatar className="size-4 rounded-none"> <Avatar className="size-4 rounded-none">
<Code size={16} /> <FileCode size={16} />
</Avatar> </Avatar>
SQL Script SQL Script
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> )}
</div> <ToggleGroupItem
)} value="dbml"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
>
<Avatar className="size-4 rounded-none">
<Code size={16} />
</Avatar>
DBML
</ToggleGroupItem>
</ToggleGroup>
</div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="text-sm font-semibold">Instructions:</div> <div className="text-sm font-semibold">Instructions:</div>
@@ -167,11 +181,16 @@ export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
showSSMSInfoDialog={showSSMSInfoDialog} showSSMSInfoDialog={showSSMSInfoDialog}
setShowSSMSInfoDialog={setShowSSMSInfoDialog} setShowSSMSInfoDialog={setShowSSMSInfoDialog}
/> />
) : ( ) : importMethod === 'ddl' ? (
<DDLInstructions <DDLInstructions
databaseType={databaseType} databaseType={databaseType}
databaseEdition={databaseEdition} databaseEdition={databaseEdition}
/> />
) : (
<DBMLInstructions
databaseType={databaseType}
databaseEdition={databaseEdition}
/>
)} )}
</div> </div>
</div> </div>

View File

@@ -0,0 +1,47 @@
import React from 'react';
import type { DatabaseType } from '@/lib/domain/database-type';
import type { DatabaseEdition } from '@/lib/domain/database-edition';
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
export interface DBMLInstructionsProps {
databaseType: DatabaseType;
databaseEdition?: DatabaseEdition;
}
export const DBMLInstructions: React.FC<DBMLInstructionsProps> = () => {
return (
<>
<div className="flex flex-col gap-1 text-sm text-primary">
<div>
Paste your DBML (Database Markup Language) schema definition
here
</div>
</div>
<div className="flex h-64 flex-col gap-1 text-sm text-primary">
<h4 className="text-xs font-medium">Example:</h4>
<CodeSnippet
className="h-full"
allowCopy={false}
editorProps={{
beforeMount: setupDBMLLanguage,
}}
code={`Table users {
id int [pk]
username varchar
email varchar
}
Table posts {
id int [pk]
user_id int [ref: > users.id]
title varchar
content text
}`}
language={'dbml'}
/>
</div>
</>
);
};

View File

@@ -43,8 +43,8 @@ const DDLInstructionsMap: Record<DatabaseType, DDLInstruction[]> = {
}, },
{ {
text: 'Execute the following command in your terminal:', text: 'Execute the following command in your terminal:',
code: `sqlite3 <database_file_path>\n.dump > <output_file_path>`, code: `sqlite3 <database_file_path>\n".schema" > <output_file_path>`,
example: `sqlite3 my_db.db\n.dump > schema_export.sql`, example: `sqlite3 my_db.db\n".schema" > schema_export.sql`,
}, },
{ {
text: 'Open the exported SQL file, copy its contents, and paste them here.', text: 'Open the exported SQL file, copy its contents, and paste them here.',

View File

@@ -73,7 +73,7 @@ export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
{hasErrors ? ( {hasErrors ? (
<div className="rounded-md border border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950"> <div className="rounded-md border border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950">
<ScrollArea className="h-24"> <ScrollArea className="h-fit max-h-24">
<div className="space-y-3 p-3 pt-2 text-red-700 dark:text-red-300"> <div className="space-y-3 p-3 pt-2 text-red-700 dark:text-red-300">
{validation?.errors {validation?.errors
.slice(0, 3) .slice(0, 3)
@@ -137,7 +137,7 @@ export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
{hasWarnings && !hasErrors ? ( {hasWarnings && !hasErrors ? (
<div className="rounded-md border border-sky-200 bg-sky-50 dark:border-sky-800 dark:bg-sky-950"> <div className="rounded-md border border-sky-200 bg-sky-50 dark:border-sky-800 dark:bg-sky-950">
<ScrollArea className="h-24"> <ScrollArea className="h-fit max-h-24">
<div className="space-y-3 p-3 pt-2 text-sky-700 dark:text-sky-300"> <div className="space-y-3 p-3 pt-2 text-sky-700 dark:text-sky-300">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-sky-700 dark:text-sky-300" /> <AlertTriangle className="mt-0.5 size-4 shrink-0 text-sky-700 dark:text-sky-300" />

View File

@@ -3,7 +3,7 @@ import { Dialog, DialogContent } from '@/components/dialog/dialog';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram'; import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useConfig } from '@/hooks/use-config'; import { useConfig } from '@/hooks/use-config';
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata'; import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
@@ -22,6 +22,11 @@ import { sqlImportToDiagram } from '@/lib/data/sql-import';
import type { SelectedTable } from '@/lib/data/import-metadata/filter-metadata'; import type { SelectedTable } from '@/lib/data/import-metadata/filter-metadata';
import { filterMetadataByTables } from '@/lib/data/import-metadata/filter-metadata'; import { filterMetadataByTables } from '@/lib/data/import-metadata/filter-metadata';
import { MAX_TABLES_WITHOUT_SHOWING_FILTER } from '../common/select-tables/constants'; import { MAX_TABLES_WITHOUT_SHOWING_FILTER } from '../common/select-tables/constants';
import {
defaultDBMLDiagramName,
importDBMLToDiagram,
} from '@/lib/dbml/dbml-import/dbml-import';
import type { ImportMethod } from '@/lib/import-method/import-method';
export interface CreateDiagramDialogProps extends BaseDialogProps {} export interface CreateDiagramDialogProps extends BaseDialogProps {}
@@ -30,11 +35,11 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
}) => { }) => {
const { diagramId } = useChartDB(); const { diagramId } = useChartDB();
const { t } = useTranslation(); const { t } = useTranslation();
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query'); const [importMethod, setImportMethod] = useState<ImportMethod>('query');
const [databaseType, setDatabaseType] = useState<DatabaseType>( const [databaseType, setDatabaseType] = useState<DatabaseType>(
DatabaseType.GENERIC DatabaseType.GENERIC
); );
const { closeCreateDiagramDialog, openImportDBMLDialog } = useDialog(); const { closeCreateDiagramDialog } = useDialog();
const { updateConfig } = useConfig(); const { updateConfig } = useConfig();
const [scriptResult, setScriptResult] = useState(''); const [scriptResult, setScriptResult] = useState('');
const [databaseEdition, setDatabaseEdition] = useState< const [databaseEdition, setDatabaseEdition] = useState<
@@ -89,6 +94,14 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
sourceDatabaseType: databaseType, sourceDatabaseType: databaseType,
targetDatabaseType: databaseType, targetDatabaseType: databaseType,
}); });
} else if (importMethod === 'dbml') {
diagram = await importDBMLToDiagram(scriptResult, {
databaseType,
});
// Update the diagram name if it's the default
if (diagram.name === defaultDBMLDiagramName) {
diagram.name = `Diagram ${diagramNumber}`;
}
} else { } else {
let metadata: DatabaseMetadata | undefined = databaseMetadata; let metadata: DatabaseMetadata | undefined = databaseMetadata;
@@ -152,10 +165,6 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
await updateConfig({ config: { defaultDiagramId: diagram.id } }); await updateConfig({ config: { defaultDiagramId: diagram.id } });
closeCreateDiagramDialog(); closeCreateDiagramDialog();
navigate(`/diagrams/${diagram.id}`); navigate(`/diagrams/${diagram.id}`);
setTimeout(
() => openImportDBMLDialog({ withCreateEmptyDiagram: true }),
700
);
}, [ }, [
databaseType, databaseType,
addDiagram, addDiagram,
@@ -164,14 +173,13 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
navigate, navigate,
updateConfig, updateConfig,
diagramNumber, diagramNumber,
openImportDBMLDialog,
]); ]);
const importNewDiagramOrFilterTables = useCallback(async () => { const importNewDiagramOrFilterTables = useCallback(async () => {
try { try {
setIsParsingMetadata(true); setIsParsingMetadata(true);
if (importMethod === 'ddl') { if (importMethod === 'ddl' || importMethod === 'dbml') {
await importNewDiagram(); await importNewDiagram();
} else { } else {
// Parse metadata asynchronously to avoid blocking the UI // Parse metadata asynchronously to avoid blocking the UI

View File

@@ -1,20 +1,28 @@
import React, { useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { ToggleGroup } from '@/components/toggle/toggle-group'; import { ToggleGroup } from '@/components/toggle/toggle-group';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { DatabaseOption } from './database-option'; import { DatabaseOption } from './database-option';
import { ExampleOption } from './example-option'; import { ExampleOption } from './example-option';
import { Button } from '@/components/button/button'; import { Button } from '@/components/button/button';
import { ChevronDown, ChevronUp } from 'lucide-react'; import { ChevronDown, ChevronUp } from 'lucide-react';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@/components/tabs/tabs';
export interface SelectDatabaseContentProps { export interface SelectDatabaseContentProps {
databaseType: DatabaseType; databaseType: DatabaseType;
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>; setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
onContinue: () => void; onContinue: (selectedDatabaseType: DatabaseType) => void;
} }
const ROW_SIZE = 3; const ROW_SIZE = 3;
const ROWS = 2; const ROWS = 2;
const TOTAL_SLOTS = ROW_SIZE * ROWS; const TOTAL_SLOTS = ROW_SIZE * ROWS;
const SUPPORTED_DB_TYPES: DatabaseType[] = [
// Transactional databases - OLTP systems optimized for frequent read/write operations
const TRANSACTIONAL_DB_TYPES: DatabaseType[] = [
DatabaseType.MYSQL, DatabaseType.MYSQL,
DatabaseType.POSTGRESQL, DatabaseType.POSTGRESQL,
DatabaseType.MARIADB, DatabaseType.MARIADB,
@@ -22,69 +30,87 @@ const SUPPORTED_DB_TYPES: DatabaseType[] = [
DatabaseType.SQL_SERVER, DatabaseType.SQL_SERVER,
DatabaseType.ORACLE, DatabaseType.ORACLE,
DatabaseType.COCKROACHDB, DatabaseType.COCKROACHDB,
DatabaseType.CLICKHOUSE,
]; ];
// Analytical databases - OLAP systems optimized for complex queries and analytics
const ANALYTICAL_DB_TYPES: DatabaseType[] = [DatabaseType.CLICKHOUSE];
export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({ export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
databaseType, databaseType,
setDatabaseType, setDatabaseType,
onContinue, onContinue,
}) => { }) => {
const [activeTab, setActiveTab] = useState<'transactional' | 'analytical'>(
'transactional'
);
const [currentRow, setCurrentRow] = useState(0); const [currentRow, setCurrentRow] = useState(0);
const currentDbTypes =
activeTab === 'transactional'
? TRANSACTIONAL_DB_TYPES
: ANALYTICAL_DB_TYPES;
const currentDatabasesTypes = useMemo( const currentDatabasesTypes = useMemo(
() => () =>
SUPPORTED_DB_TYPES.slice( currentDbTypes.slice(
currentRow * ROW_SIZE, currentRow * ROW_SIZE,
currentRow * ROW_SIZE + TOTAL_SLOTS currentRow * ROW_SIZE + TOTAL_SLOTS
), ),
[currentRow] [currentRow, currentDbTypes]
); );
const hasNextRow = useMemo( const hasNextRow = useMemo(
() => (currentRow + 1) * ROW_SIZE < SUPPORTED_DB_TYPES.length, () => (currentRow + 1) * ROW_SIZE < currentDbTypes.length,
[currentRow] [currentRow, currentDbTypes]
); );
const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]); const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]);
const toggleRow = () => { const toggleRow = useCallback(() => {
if (currentRow === 0 && hasNextRow) { if (currentRow === 0 && hasNextRow) {
setCurrentRow(currentRow + 1); setCurrentRow(currentRow + 1);
} else if (currentRow > 0) { } else if (currentRow > 0) {
setCurrentRow(currentRow - 1); setCurrentRow(currentRow - 1);
} }
}; }, [currentRow, hasNextRow]);
return ( const handleTabChange = useCallback((value: string) => {
<div className="flex flex-1 flex-col items-center justify-center gap-4"> setActiveTab(value as 'transactional' | 'analytical');
<ToggleGroup setCurrentRow(0); // Reset to first row when switching tabs
value={databaseType} }, []);
onValueChange={(value: DatabaseType) => {
if (!value) {
setDatabaseType(DatabaseType.GENERIC);
} else {
setDatabaseType(value);
onContinue();
}
}}
type="single"
className="grid grid-flow-row grid-cols-3 gap-6"
>
{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"> const renderDatabaseGrid = useCallback(
() => (
<div className="flex min-h-[280px] flex-col md:min-h-[370px]">
<ToggleGroup
value={databaseType}
onValueChange={(value: DatabaseType) => {
if (!value) {
setDatabaseType(DatabaseType.GENERIC);
} else {
setDatabaseType(value);
onContinue(value);
}
}}
type="single"
className="grid grid-flow-row grid-cols-3 content-start gap-4"
>
{Array.from({ length: TOTAL_SLOTS }).map((_, index) =>
currentDatabasesTypes?.[index] ? (
<DatabaseOption
key={currentDatabasesTypes[index]}
type={currentDatabasesTypes[index]}
/>
) : null
)}
</ToggleGroup>
<div className="mt-auto flex flex-col gap-1 pt-4">
{hasNextRow || hasPreviousRow ? ( {hasNextRow || hasPreviousRow ? (
<Button <Button
variant="ghost" variant="ghost"
onClick={toggleRow} onClick={toggleRow}
className="col-span-3 h-8" className="h-8"
> >
{currentRow === 0 ? ( {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"> <div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 py-3 text-center md:h-12">
@@ -105,7 +131,55 @@ export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
) : null} ) : null}
<ExampleOption /> <ExampleOption />
</div> </div>
</ToggleGroup> </div>
),
[
databaseType,
currentDatabasesTypes,
hasNextRow,
hasPreviousRow,
onContinue,
setDatabaseType,
toggleRow,
currentRow,
]
);
return (
<div className="flex flex-1 flex-col items-center gap-2">
<Tabs
defaultValue="transactional"
value={activeTab}
onValueChange={handleTabChange}
className="w-auto"
>
<TabsList className="mb-2 grid size-auto grid-cols-2 gap-1 rounded-xl border bg-background p-1">
<TabsTrigger
value="transactional"
className="gap-1.5 rounded-lg px-3 py-1 text-sm font-medium transition-all data-[state=active]:bg-sky-600 data-[state=active]:text-white data-[state=inactive]:text-muted-foreground data-[state=active]:shadow-sm data-[state=inactive]:hover:bg-muted/50 data-[state=inactive]:hover:text-foreground dark:data-[state=active]:bg-sky-500"
>
Transactional
</TabsTrigger>
<TabsTrigger
value="analytical"
className="gap-1.5 rounded-lg px-3 py-1 text-sm font-medium transition-all data-[state=active]:bg-sky-600 data-[state=active]:text-white data-[state=inactive]:text-muted-foreground data-[state=active]:shadow-sm data-[state=inactive]:hover:bg-muted/50 data-[state=inactive]:hover:text-foreground dark:data-[state=active]:bg-sky-500"
>
Analytical
</TabsTrigger>
</TabsList>
<TabsContent
value="transactional"
className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0"
>
{renderDatabaseGrid()}
</TabsContent>
<TabsContent
value="analytical"
className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0"
>
{renderDatabaseGrid()}
</TabsContent>
</Tabs>
</div> </div>
); );
}; };

View File

@@ -69,6 +69,7 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
type="button" type="button"
variant="outline" variant="outline"
onClick={createNewDiagram} onClick={createNewDiagram}
disabled={databaseType === DatabaseType.GENERIC}
> >
{t('new_diagram_dialog.empty_diagram')} {t('new_diagram_dialog.empty_diagram')}
</Button> </Button>

View File

@@ -218,8 +218,14 @@ export const CreateRelationshipDialog: React.FC<
closeCreateRelationshipDialog(); closeCreateRelationshipDialog();
} }
}} }}
modal={false}
> >
<DialogContent className="flex flex-col overflow-y-auto" showClose> <DialogContent
className="flex flex-col overflow-y-auto"
showClose
forceOverlay
onInteractOutside={(e) => e.preventDefault()}
>
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{t('create_relationship_dialog.title')} {t('create_relationship_dialog.title')}

View File

@@ -17,15 +17,21 @@ import { useDialog } from '@/hooks/use-dialog';
import { import {
exportBaseSQL, exportBaseSQL,
exportSQL, exportSQL,
} from '@/lib/data/export-metadata/export-sql-script'; } from '@/lib/data/sql-export/export-sql-script';
import { databaseTypeToLabelMap } from '@/lib/databases'; import { databaseTypeToLabelMap } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
import { Annoyed, Sparkles } from 'lucide-react'; import { Annoyed, Sparkles } from 'lucide-react';
import React, { useCallback, useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
import {
filterDependency,
filterRelationship,
filterTable,
} from '@/lib/domain/diagram-filter/filter';
import { defaultSchemas } from '@/lib/data/default-schemas';
export interface ExportSQLDialogProps extends BaseDialogProps { export interface ExportSQLDialogProps extends BaseDialogProps {
targetDatabaseType: DatabaseType; targetDatabaseType: DatabaseType;
@@ -36,7 +42,8 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
targetDatabaseType, targetDatabaseType,
}) => { }) => {
const { closeExportSQLDialog } = useDialog(); const { closeExportSQLDialog } = useDialog();
const { currentDiagram, filteredSchemas } = useChartDB(); const { currentDiagram } = useChartDB();
const { filter } = useDiagramFilter();
const { t } = useTranslation(); const { t } = useTranslation();
const [script, setScript] = React.useState<string>(); const [script, setScript] = React.useState<string>();
const [error, setError] = React.useState<boolean>(false); const [error, setError] = React.useState<boolean>(false);
@@ -48,7 +55,16 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const filteredDiagram: Diagram = { const filteredDiagram: Diagram = {
...currentDiagram, ...currentDiagram,
tables: currentDiagram.tables?.filter((table) => tables: currentDiagram.tables?.filter((table) =>
shouldShowTablesBySchemaFilter(table, filteredSchemas) filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
), ),
relationships: currentDiagram.relationships?.filter((rel) => { relationships: currentDiagram.relationships?.filter((rel) => {
const sourceTable = currentDiagram.tables?.find( const sourceTable = currentDiagram.tables?.find(
@@ -60,11 +76,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return ( return (
sourceTable && sourceTable &&
targetTable && targetTable &&
shouldShowTablesBySchemaFilter( filterRelationship({
sourceTable, tableA: {
filteredSchemas id: sourceTable.id,
) && schema: sourceTable.schema,
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas) },
tableB: {
id: targetTable.id,
schema: targetTable.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
); );
}), }),
dependencies: currentDiagram.dependencies?.filter((dep) => { dependencies: currentDiagram.dependencies?.filter((dep) => {
@@ -77,11 +102,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return ( return (
table && table &&
dependentTable && dependentTable &&
shouldShowTablesBySchemaFilter(table, filteredSchemas) && filterDependency({
shouldShowTablesBySchemaFilter( tableA: {
dependentTable, id: table.id,
filteredSchemas schema: table.schema,
) },
tableB: {
id: dependentTable.id,
schema: dependentTable.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
); );
}), }),
}; };
@@ -101,7 +135,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
signal: abortControllerRef.current?.signal, signal: abortControllerRef.current?.signal,
}); });
} }
}, [targetDatabaseType, currentDiagram, filteredSchemas]); }, [targetDatabaseType, currentDiagram, filter]);
useEffect(() => { useEffect(() => {
if (!dialog.open) { if (!dialog.open) {

View File

@@ -7,7 +7,7 @@ import type { DatabaseEdition } from '@/lib/domain/database-edition';
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata'; import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata'; import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram'; import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack'; import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
@@ -15,6 +15,8 @@ import { useReactFlow } from '@xyflow/react';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
import { useAlert } from '@/context/alert-context/alert-context'; import { useAlert } from '@/context/alert-context/alert-context';
import { sqlImportToDiagram } from '@/lib/data/sql-import'; import { sqlImportToDiagram } from '@/lib/data/sql-import';
import { importDBMLToDiagram } from '@/lib/dbml/dbml-import/dbml-import';
import type { ImportMethod } from '@/lib/import-method/import-method';
export interface ImportDatabaseDialogProps extends BaseDialogProps { export interface ImportDatabaseDialogProps extends BaseDialogProps {
databaseType: DatabaseType; databaseType: DatabaseType;
@@ -24,7 +26,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
dialog, dialog,
databaseType, databaseType,
}) => { }) => {
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query'); const [importMethod, setImportMethod] = useState<ImportMethod>('query');
const { closeImportDatabaseDialog } = useDialog(); const { closeImportDatabaseDialog } = useDialog();
const { showAlert } = useAlert(); const { showAlert } = useAlert();
const { const {
@@ -65,6 +67,10 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
sourceDatabaseType: databaseType, sourceDatabaseType: databaseType,
targetDatabaseType: databaseType, targetDatabaseType: databaseType,
}); });
} else if (importMethod === 'dbml') {
diagram = await importDBMLToDiagram(scriptResult, {
databaseType,
});
} else { } else {
const databaseMetadata: DatabaseMetadata = const databaseMetadata: DatabaseMetadata =
loadDatabaseMetadata(scriptResult); loadDatabaseMetadata(scriptResult);

View File

@@ -1,359 +0,0 @@
import React, {
useCallback,
useEffect,
useState,
Suspense,
useRef,
} from 'react';
import type * 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,
sanitizeDBML,
preprocessDBML,
} from '@/lib/dbml/dbml-import/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 type { DBTable } from '@/lib/domain/db-table';
import { useToast } from '@/components/toast/use-toast';
import { Spinner } from '@/components/spinner/spinner';
import { debounce } from '@/lib/utils';
import { parseDBMLError } from '@/lib/dbml/dbml-import/dbml-import-error';
import {
clearErrorHighlight,
highlightErrorLine,
} from '@/components/code-snippet/dbml/utils';
export interface ImportDBMLDialogProps extends BaseDialogProps {
withCreateEmptyDiagram?: boolean;
}
export const ImportDBMLDialog: React.FC<ImportDBMLDialogProps> = ({
dialog,
withCreateEmptyDiagram,
}) => {
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 clearDecorations = useCallback(() => {
clearErrorHighlight(decorationsCollection.current);
}, []);
const validateDBML = useCallback(
async (content: string) => {
// Clear previous errors
setErrorMessage(undefined);
clearDecorations();
if (!content.trim()) return;
try {
const preprocessedContent = preprocessDBML(content);
const sanitizedContent = sanitizeDBML(preprocessedContent);
const parser = new Parser();
parser.parse(sanitizedContent, 'dbml');
} catch (e) {
const parsedError = parseDBMLError(e);
if (parsedError) {
setErrorMessage(
t('import_dbml_dialog.error.description') +
` (1 error found - in line ${parsedError.line})`
);
highlightErrorLine({
error: parsedError,
model: editorRef.current?.getModel(),
editorDecorationsCollection:
decorationsCollection.current,
});
} else {
setErrorMessage(
e instanceof Error ? e.message : JSON.stringify(e)
);
}
}
},
[clearDecorations, 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: DBTable) =>
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: DBTable) =>
table.id === relationship.sourceTableId
);
const targetTable = tables.find(
(table: DBTable) =>
table.id === relationship.targetTableId
);
if (!sourceTable || !targetTable) return true;
const replacementSourceTable = importedDiagram.tables?.find(
(table: DBTable) =>
table.name === sourceTable.name &&
table.schema === sourceTable.schema
);
const replacementTargetTable = importedDiagram.tables?.find(
(table: DBTable) =>
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 w-full flex-col md:max-w-[900px]"
showClose
>
<DialogHeader>
<DialogTitle>
{withCreateEmptyDiagram
? t('import_dbml_dialog.example_title')
: 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">
{withCreateEmptyDiagram
? t('import_dbml_dialog.skip_and_empty')
: 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}
>
{withCreateEmptyDiagram
? t('import_dbml_dialog.show_example')
: t('import_dbml_dialog.import')}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,98 @@
import React, { useCallback } from 'react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/dropdown-menu/dropdown-menu';
import { Button } from '@/components/button/button';
import { Ellipsis, Layers2, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
import { useChartDB } from '@/hooks/use-chartdb';
import type { Diagram } from '@/lib/domain';
import { useStorage } from '@/hooks/use-storage';
import { cloneDiagram } from '@/lib/clone';
import { useTranslation } from 'react-i18next';
interface DiagramRowActionsMenuProps {
diagram: Diagram;
onOpen: () => void;
refetch: () => void;
numberOfDiagrams: number;
}
export const DiagramRowActionsMenu: React.FC<DiagramRowActionsMenuProps> = ({
diagram,
onOpen,
refetch,
numberOfDiagrams,
}) => {
const { diagramId } = useChartDB();
const { deleteDiagram, addDiagram } = useStorage();
const { t } = useTranslation();
const onDelete = useCallback(async () => {
deleteDiagram(diagram.id);
refetch();
if (diagram.id === diagramId || numberOfDiagrams <= 1) {
window.location.href = '/';
}
}, [deleteDiagram, diagram.id, diagramId, refetch, numberOfDiagrams]);
const onDuplicate = useCallback(async () => {
const duplicatedDiagram = cloneDiagram(diagram);
const diagramToAdd = duplicatedDiagram.diagram;
if (!diagramToAdd) {
return;
}
diagramToAdd.name = `${diagram.name} (Copy)`;
addDiagram({ diagram: diagramToAdd });
refetch();
}, [addDiagram, refetch, diagram]);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-8 p-0"
onClick={(e) => e.stopPropagation()}
>
<Ellipsis className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={onOpen}
className="flex justify-between gap-4"
>
{t('open_diagram_dialog.diagram_actions.open')}
<SquareArrowOutUpRight className="size-3.5" />
</DropdownMenuItem>
<DropdownMenuItem
onClick={onDuplicate}
className="flex justify-between gap-4"
>
{t('open_diagram_dialog.diagram_actions.duplicate')}
<Layers2 className="size-3.5" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={onDelete}
className="flex justify-between gap-4 text-red-700"
>
{t('open_diagram_dialog.diagram_actions.delete')}
<Trash2 className="size-3.5 text-red-700" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

View File

@@ -27,6 +27,7 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
import { useDebounce } from '@/hooks/use-debounce'; import { useDebounce } from '@/hooks/use-debounce';
import { DiagramRowActionsMenu } from './diagram-row-actions-menu/diagram-row-actions-menu';
export interface OpenDiagramDialogProps extends BaseDialogProps { export interface OpenDiagramDialogProps extends BaseDialogProps {
canClose?: boolean; canClose?: boolean;
@@ -46,21 +47,22 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
string | undefined string | undefined
>(); >();
useEffect(() => { const fetchDiagrams = useCallback(async () => {
setSelectedDiagramId(undefined); const diagrams = await listDiagrams({ includeTables: true });
}, [dialog.open]); setDiagrams(
diagrams.sort(
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
)
);
}, [listDiagrams]);
useEffect(() => { useEffect(() => {
const fetchDiagrams = async () => { if (!dialog.open) {
const diagrams = await listDiagrams({ includeTables: true }); return;
setDiagrams( }
diagrams.sort( setSelectedDiagramId(undefined);
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
)
);
};
fetchDiagrams(); fetchDiagrams();
}, [listDiagrams, setDiagrams, dialog.open]); }, [dialog.open, fetchDiagrams]);
const openDiagram = useCallback( const openDiagram = useCallback(
(diagramId: string) => { (diagramId: string) => {
@@ -166,6 +168,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
'open_diagram_dialog.table_columns.tables_count' 'open_diagram_dialog.table_columns.tables_count'
)} )}
</TableHead> </TableHead>
<TableHead />
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@@ -221,6 +224,19 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
<TableCell className="text-center"> <TableCell className="text-center">
{diagram.tables?.length} {diagram.tables?.length}
</TableCell> </TableCell>
<TableCell className="items-center p-0 pr-1 text-right">
<DiagramRowActionsMenu
diagram={diagram}
onOpen={() => {
openDiagram(diagram.id);
closeOpenDiagramDialog();
}}
numberOfDiagrams={
diagrams.length
}
refetch={fetchDiagrams}
/>
</TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>

View File

@@ -44,7 +44,7 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
allowSchemaCreation = false, allowSchemaCreation = false,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { databaseType, filteredSchemas, filterSchemas } = useChartDB(); const { databaseType } = useChartDB();
const [selectedSchemaId, setSelectedSchemaId] = useState<string>( const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
table?.schema table?.schema
? schemaNameToSchemaId(table.schema) ? schemaNameToSchemaId(table.schema)
@@ -93,7 +93,6 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
const { closeTableSchemaDialog } = useDialog(); const { closeTableSchemaDialog } = useDialog();
const handleConfirm = useCallback(() => { const handleConfirm = useCallback(() => {
let createdSchemaId: string;
if (isCreatingNew && newSchemaName.trim()) { if (isCreatingNew && newSchemaName.trim()) {
const newSchema: DBSchema = { const newSchema: DBSchema = {
id: schemaNameToSchemaId(newSchemaName.trim()), id: schemaNameToSchemaId(newSchemaName.trim()),
@@ -101,30 +100,14 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
tableCount: 0, tableCount: 0,
}; };
createdSchemaId = newSchema.id;
onConfirm({ schema: newSchema }); onConfirm({ schema: newSchema });
} else { } else {
const schema = schemas.find((s) => s.id === selectedSchemaId); const schema = schemas.find((s) => s.id === selectedSchemaId);
if (!schema) return; if (!schema) return;
createdSchemaId = schema.id;
onConfirm({ schema }); onConfirm({ schema });
} }
}, [onConfirm, selectedSchemaId, schemas, isCreatingNew, newSchemaName]);
filterSchemas([
...(filteredSchemas ?? schemas.map((s) => s.id)),
createdSchemaId,
]);
}, [
onConfirm,
selectedSchemaId,
schemas,
isCreatingNew,
newSchemaName,
filteredSchemas,
filterSchemas,
]);
const schemaOptions: SelectBoxOption[] = useMemo( const schemaOptions: SelectBoxOption[] = useMemo(
() => () =>

181
src/hooks/use-focus-on.ts Normal file
View File

@@ -0,0 +1,181 @@
import { useCallback } from 'react';
import { useReactFlow } from '@xyflow/react';
import { useLayout } from '@/hooks/use-layout';
import { useBreakpoint } from '@/hooks/use-breakpoint';
interface FocusOptions {
select?: boolean;
}
export const useFocusOn = () => {
const { fitView, setNodes, setEdges } = useReactFlow();
const { hideSidePanel } = useLayout();
const { isMd: isDesktop } = useBreakpoint('md');
const focusOnArea = useCallback(
(areaId: string, options: FocusOptions = {}) => {
const { select = true } = options;
if (select) {
setNodes((nodes) =>
nodes.map((node) =>
node.id === areaId
? {
...node,
selected: true,
}
: {
...node,
selected: false,
}
)
);
}
fitView({
duration: 500,
maxZoom: 1,
minZoom: 1,
nodes: [
{
id: areaId,
},
],
});
if (!isDesktop) {
hideSidePanel();
}
},
[fitView, setNodes, hideSidePanel, isDesktop]
);
const focusOnTable = useCallback(
(tableId: string, options: FocusOptions = {}) => {
const { select = true } = options;
if (select) {
setNodes((nodes) =>
nodes.map((node) =>
node.id === tableId
? {
...node,
selected: true,
}
: {
...node,
selected: false,
}
)
);
}
fitView({
duration: 500,
maxZoom: 1,
minZoom: 1,
nodes: [
{
id: tableId,
},
],
});
if (!isDesktop) {
hideSidePanel();
}
},
[fitView, setNodes, hideSidePanel, isDesktop]
);
const focusOnNote = useCallback(
(noteId: string, options: FocusOptions = {}) => {
const { select = true } = options;
if (select) {
setNodes((nodes) =>
nodes.map((node) =>
node.id === noteId
? {
...node,
selected: true,
}
: {
...node,
selected: false,
}
)
);
}
fitView({
duration: 500,
maxZoom: 1,
minZoom: 1,
nodes: [
{
id: noteId,
},
],
});
if (!isDesktop) {
hideSidePanel();
}
},
[fitView, setNodes, hideSidePanel, isDesktop]
);
const focusOnRelationship = useCallback(
(
relationshipId: string,
sourceTableId: string,
targetTableId: string,
options: FocusOptions = {}
) => {
const { select = true } = options;
if (select) {
setEdges((edges) =>
edges.map((edge) =>
edge.id === relationshipId
? {
...edge,
selected: true,
}
: {
...edge,
selected: false,
}
)
);
}
fitView({
duration: 500,
maxZoom: 1,
minZoom: 1,
nodes: [
{
id: sourceTableId,
},
{
id: targetTableId,
},
],
});
if (!isDesktop) {
hideSidePanel();
}
},
[fitView, setEdges, hideSidePanel, isDesktop]
);
return {
focusOnArea,
focusOnTable,
focusOnNote,
focusOnRelationship,
};
};

View File

@@ -0,0 +1,379 @@
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
import { useChartDB } from './use-chartdb';
import { useDebounce } from './use-debounce-v2';
import type { DatabaseType, DBField, DBTable } from '@/lib/domain';
import type {
SelectBoxOption,
SelectBoxProps,
} from '@/components/select-box/select-box';
import {
dataTypeDataToDataType,
sortedDataTypeMap,
supportsArrayDataType,
autoIncrementAlwaysOn,
requiresNotNull,
} from '@/lib/data/data-types/data-types';
import { generateDBFieldSuffix } from '@/lib/domain/db-field';
import type { DataTypeData } from '@/lib/data/data-types/data-types';
const generateFieldRegexPatterns = (
dataType: DataTypeData,
databaseType: DatabaseType
): {
regex?: string;
extractRegex?: RegExp;
} => {
const typeName = dataType.name;
const supportsArrays = supportsArrayDataType(dataType.id, databaseType);
const arrayPattern = supportsArrays ? '(\\[\\])?' : '';
if (!dataType.fieldAttributes) {
// For types without field attributes, support plain type + optional array notation
return {
regex: `^${typeName}${arrayPattern}$`,
extractRegex: new RegExp(`^${typeName}${arrayPattern}$`),
};
}
const fieldAttributes = dataType.fieldAttributes;
if (fieldAttributes.hasCharMaxLength) {
if (fieldAttributes.hasCharMaxLengthOption) {
return {
regex: `^${typeName}\\((\\d+|[mM][aA][xX])\\)${arrayPattern}$`,
extractRegex: supportsArrays
? /\((\d+|max)\)(\[\])?/i
: /\((\d+|max)\)/i,
};
}
return {
regex: `^${typeName}\\(\\d+\\)${arrayPattern}$`,
extractRegex: supportsArrays ? /\((\d+)\)(\[\])?/ : /\((\d+)\)/,
};
}
if (fieldAttributes.precision && fieldAttributes.scale) {
return {
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*(?:,\\s*\\d+\\s*)?\\)${arrayPattern}$`,
extractRegex: new RegExp(
`${typeName}\\s*\\(\\s*(\\d+)\\s*(?:,\\s*(\\d+)\\s*)?\\)${arrayPattern}`
),
};
}
if (fieldAttributes.precision) {
return {
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*\\)${arrayPattern}$`,
extractRegex: supportsArrays ? /\((\d+)\)(\[\])?/ : /\((\d+)\)/,
};
}
return { regex: undefined, extractRegex: undefined };
};
export const useUpdateTableField = (
table: DBTable,
field: DBField,
customUpdateField?: (attrs: Partial<DBField>) => void
) => {
const {
databaseType,
customTypes,
updateField: chartDBUpdateField,
removeField: chartDBRemoveField,
} = useChartDB();
// Local state for responsive UI
const [localFieldName, setLocalFieldName] = useState(field.name);
const [localNullable, setLocalNullable] = useState(field.nullable);
const [localPrimaryKey, setLocalPrimaryKey] = useState(field.primaryKey);
const lastFieldNameRef = useRef<string>(field.name);
useEffect(() => {
if (localFieldName === lastFieldNameRef.current) {
lastFieldNameRef.current = field.name;
setLocalFieldName(field.name);
}
}, [field.name, localFieldName]);
// Update local state when field properties change externally
useEffect(() => {
setLocalNullable(field.nullable);
setLocalPrimaryKey(field.primaryKey);
}, [field.nullable, field.primaryKey]);
// Use custom updateField if provided, otherwise use the chartDB one
const updateField = useMemo(
() =>
customUpdateField
? (
_tableId: string,
_fieldId: string,
attrs: Partial<DBField>
) => customUpdateField(attrs)
: chartDBUpdateField,
[customUpdateField, chartDBUpdateField]
);
// Calculate primary key fields for validation
const primaryKeyFields = useMemo(() => {
return table.fields.filter((f) => f.primaryKey);
}, [table.fields]);
const primaryKeyCount = useMemo(
() => primaryKeyFields.length,
[primaryKeyFields.length]
);
// Generate data type options for select box
const dataFieldOptions = useMemo(() => {
const standardTypes: SelectBoxOption[] = sortedDataTypeMap[
databaseType
].map((type) => {
const regexPatterns = generateFieldRegexPatterns(
type,
databaseType
);
return {
label: type.name,
value: type.id,
regex: regexPatterns.regex,
extractRegex: regexPatterns.extractRegex,
group: customTypes?.length ? 'Standard Types' : undefined,
};
});
if (!customTypes?.length) {
return standardTypes;
}
// Add custom types as options
const customTypeOptions: SelectBoxOption[] = customTypes.map(
(type) => ({
label: type.name,
value: type.name,
description:
type.kind === 'enum' ? `${type.values?.join(' | ')}` : '',
group: 'Custom Types',
})
);
return [...standardTypes, ...customTypeOptions];
}, [databaseType, customTypes]);
// Handle data type change
const handleDataTypeChange = useCallback<
NonNullable<SelectBoxProps['onChange']>
>(
(value, regexMatches) => {
const dataType = sortedDataTypeMap[databaseType].find(
(v) => v.id === value
) ?? {
id: value as string,
name: value as string,
};
let characterMaximumLength: string | undefined = undefined;
let precision: number | undefined = undefined;
let scale: number | undefined = undefined;
let isArray: boolean | undefined = undefined;
if (regexMatches?.length) {
// Check if the last captured group is the array indicator []
const lastMatch = regexMatches[regexMatches.length - 1];
const hasArrayIndicator = lastMatch === '[]';
if (dataType?.fieldAttributes?.hasCharMaxLength) {
characterMaximumLength = regexMatches[1]?.toLowerCase();
} else if (
dataType?.fieldAttributes?.precision &&
dataType?.fieldAttributes?.scale
) {
precision = parseInt(regexMatches[1]);
scale = regexMatches[2]
? parseInt(regexMatches[2])
: undefined;
} else if (dataType?.fieldAttributes?.precision) {
precision = parseInt(regexMatches[1]);
}
// Set isArray if the array indicator was found and the type supports arrays
if (hasArrayIndicator) {
const typeId = value as string;
if (supportsArrayDataType(typeId, databaseType)) {
isArray = true;
}
} else {
// Explicitly set to false/undefined if no array indicator
isArray = undefined;
}
} else {
if (
dataType?.fieldAttributes?.hasCharMaxLength &&
field.characterMaximumLength
) {
characterMaximumLength = field.characterMaximumLength;
}
if (dataType?.fieldAttributes?.precision && field.precision) {
precision = field.precision;
}
if (dataType?.fieldAttributes?.scale && field.scale) {
scale = field.scale;
}
}
const newTypeName = dataType?.name ?? (value as string);
const typeRequiresNotNull = requiresNotNull(newTypeName);
const shouldForceIncrement = autoIncrementAlwaysOn(newTypeName);
updateField(table.id, field.id, {
characterMaximumLength,
precision,
scale,
isArray,
...(typeRequiresNotNull ? { nullable: false } : {}),
increment: shouldForceIncrement ? true : undefined,
default: undefined,
type: dataTypeDataToDataType(
dataType ?? {
id: value as string,
name: value as string,
}
),
});
},
[
updateField,
databaseType,
field.characterMaximumLength,
field.precision,
field.scale,
field.id,
table.id,
]
);
// Debounced update for field name
const debouncedNameUpdate = useDebounce(
useCallback(
(value: string) => {
if (value.trim() !== field.name) {
updateField(table.id, field.id, { name: value });
}
},
[updateField, table.id, field.id, field.name]
),
300 // 300ms debounce for text input
);
// Debounced update for nullable toggle
const debouncedNullableUpdate = useDebounce(
useCallback(
(value: boolean) => {
const updates: Partial<DBField> = { nullable: value };
// If setting to nullable, clear increment (auto-increment requires NOT NULL)
if (value && field.increment) {
updates.increment = undefined;
}
updateField(table.id, field.id, updates);
},
[updateField, table.id, field.id, field.increment]
),
100 // 100ms debounce for toggle
);
// Debounced update for primary key toggle
const debouncedPrimaryKeyUpdate = useDebounce(
useCallback(
(value: boolean, primaryKeyCount: number) => {
if (value) {
// When setting as primary key
const updates: Partial<DBField> = {
primaryKey: true,
};
// Only auto-set unique if this will be the only primary key
if (primaryKeyCount === 0) {
updates.unique = true;
}
updateField(table.id, field.id, updates);
} else {
// When removing primary key
updateField(table.id, field.id, {
primaryKey: false,
});
}
},
[updateField, table.id, field.id]
),
100 // 100ms debounce for toggle
);
// Handle primary key toggle with optimistic update
const handlePrimaryKeyToggle = useCallback(
(value: boolean) => {
setLocalPrimaryKey(value);
debouncedPrimaryKeyUpdate(value, primaryKeyCount);
},
[primaryKeyCount, debouncedPrimaryKeyUpdate]
);
// Handle nullable toggle with optimistic update
const handleNullableToggle = useCallback(
(value: boolean) => {
setLocalNullable(value);
debouncedNullableUpdate(value);
},
[debouncedNullableUpdate]
);
// Handle name change with optimistic update
const handleNameChange = useCallback(
(value: string) => {
setLocalFieldName(value);
debouncedNameUpdate(value);
},
[debouncedNameUpdate]
);
// Utility function to generate field suffix for display
const generateFieldSuffix = useCallback(
(typeId?: string) => {
return generateDBFieldSuffix(
{
...field,
isArray: field.isArray && typeId === field.type.id,
},
{
databaseType,
forceExtended: true,
typeId,
}
);
},
[field, databaseType]
);
const removeField = useCallback(() => {
chartDBRemoveField(table.id, field.id);
}, [chartDBRemoveField, table.id, field.id]);
return {
dataFieldOptions,
handleDataTypeChange,
handlePrimaryKeyToggle,
handleNullableToggle,
handleNameChange,
generateFieldSuffix,
primaryKeyCount,
fieldName: localFieldName,
nullable: localNullable,
primaryKey: localPrimaryKey,
removeField,
};
};

View File

@@ -0,0 +1,42 @@
import { useCallback, useState, useEffect } from 'react';
import { useChartDB } from './use-chartdb';
import { useDebounce } from './use-debounce-v2';
import type { DBTable } from '@/lib/domain';
// Hook for updating table properties with debouncing for performance
export const useUpdateTable = (table: DBTable) => {
const { updateTable: chartDBUpdateTable } = useChartDB();
const [localTableName, setLocalTableName] = useState(table.name);
// Debounced update function
const debouncedUpdate = useDebounce(
useCallback(
(value: string) => {
if (value.trim() && value.trim() !== table.name) {
chartDBUpdateTable(table.id, { name: value.trim() });
}
},
[chartDBUpdateTable, table.id, table.name]
),
1000 // 1000ms debounce
);
// Update local state immediately for responsive UI
const handleTableNameChange = useCallback(
(value: string) => {
setLocalTableName(value);
debouncedUpdate(value);
},
[debouncedUpdate]
);
// Update local state when table name changes externally
useEffect(() => {
setLocalTableName(table.name);
}, [table.name]);
return {
tableName: localTableName,
handleTableNameChange,
};
};

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ar: LanguageTranslation = { export const ar: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'جديد',
browse: 'تصفح',
tables: 'الجداول',
refs: 'المراجع',
dependencies: 'التبعيات',
custom_types: 'الأنواع المخصصة',
visuals: 'مرئيات',
},
menu: { menu: {
file: { actions: {
file: 'ملف', actions: 'الإجراءات',
new: 'جديد', new: 'جديد...',
open: 'فتح', browse: 'تصفح...',
save: 'حفظ', save: 'حفظ',
import: 'استيراد قاعدة بيانات', import: 'استيراد قاعدة بيانات',
export_sql: 'SQL تصدير', export_sql: 'SQL تصدير',
export_as: 'تصدير كـ', export_as: 'تصدير كـ',
delete_diagram: 'حذف الرسم البياني', delete_diagram: 'حذف',
exit: 'خروج',
}, },
edit: { edit: {
edit: 'تحرير', edit: 'تحرير',
@@ -29,6 +37,7 @@ export const ar: LanguageTranslation = {
hide_field_attributes: 'إخفاء خصائص الحقل', hide_field_attributes: 'إخفاء خصائص الحقل',
show_field_attributes: 'إظهار خصائص الحقل', show_field_attributes: 'إظهار خصائص الحقل',
zoom_on_scroll: 'تكبير/تصغير عند التمرير', zoom_on_scroll: 'تكبير/تصغير عند التمرير',
show_views: 'عروض قاعدة البيانات',
theme: 'المظهر', theme: 'المظهر',
show_dependencies: 'إظهار الاعتمادات', show_dependencies: 'إظهار الاعتمادات',
hide_dependencies: 'إخفاء الاعتمادات', hide_dependencies: 'إخفاء الاعتمادات',
@@ -65,22 +74,13 @@ export const ar: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'إعادة ترتيب الرسم البياني', title: 'ترتيب تلقائي للرسم البياني',
description: description:
'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟', 'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
reorder: 'إعادة ترتيب', reorder: 'ترتيب تلقائي',
cancel: 'إلغاء', cancel: 'إلغاء',
}, },
multiple_schemas_alert: {
title: 'مخططات متعددة',
description:
'{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
// TODO: Translate
show_me: 'Show me',
none: 'لا شيء',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'فشل النسخ', title: 'فشل النسخ',
@@ -115,14 +115,11 @@ export const ar: LanguageTranslation = {
copied: '!تم النسخ', copied: '!تم النسخ',
side_panel: { side_panel: {
schema: ':المخطط',
filter_by_schema: 'تصفية حسب المخطط',
search_schema: '...بحث في المخطط',
no_schemas_found: '.لم يتم العثور على مخططات',
view_all_options: '...عرض جميع الخيارات', view_all_options: '...عرض جميع الخيارات',
tables_section: { tables_section: {
tables: 'الجداول', tables: 'الجداول',
add_table: 'إضافة جدول', add_table: 'إضافة جدول',
add_view: 'إضافة عرض',
filter: 'تصفية', filter: 'تصفية',
collapse: 'طي الكل', collapse: 'طي الكل',
// TODO: Translate // TODO: Translate
@@ -148,6 +145,7 @@ export const ar: LanguageTranslation = {
field_actions: { field_actions: {
title: 'خصائص الحقل', title: 'خصائص الحقل',
unique: 'فريد', unique: 'فريد',
auto_increment: 'زيادة تلقائية',
comments: 'تعليقات', comments: 'تعليقات',
no_comments: 'لا يوجد تعليقات', no_comments: 'لا يوجد تعليقات',
delete_field: 'حذف الحقل', delete_field: 'حذف الحقل',
@@ -162,6 +160,7 @@ export const ar: LanguageTranslation = {
title: 'خصائص الفهرس', title: 'خصائص الفهرس',
name: 'الإسم', name: 'الإسم',
unique: 'فريد', unique: 'فريد',
index_type: 'نوع الفهرس',
delete_index: 'حذف الفهرس', delete_index: 'حذف الفهرس',
}, },
table_actions: { table_actions: {
@@ -178,12 +177,15 @@ export const ar: LanguageTranslation = {
description: 'أنشئ جدولاً للبدء', description: 'أنشئ جدولاً للبدء',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'العلاقات', refs: 'المراجع',
filter: 'تصفية', filter: 'تصفية',
add_relationship: 'إضافة علاقة',
collapse: 'طي الكل', collapse: 'طي الكل',
add_relationship: 'إضافة علاقة',
relationships: 'العلاقات',
dependencies: 'الاعتمادات',
relationship: { relationship: {
relationship: 'العلاقة',
primary: 'الجدول الأساسي', primary: 'الجدول الأساسي',
foreign: 'الجدول المرتبط', foreign: 'الجدول المرتبط',
cardinality: 'الكاردينالية', cardinality: 'الكاردينالية',
@@ -193,16 +195,8 @@ export const ar: LanguageTranslation = {
delete_relationship: 'حذف', delete_relationship: 'حذف',
}, },
}, },
empty_state: {
title: 'لا توجد علاقات',
description: 'إنشئ علاقة لربط الجداول',
},
},
dependencies_section: {
dependencies: 'الاعتمادات',
filter: 'تصفية',
collapse: 'طي الكل',
dependency: { dependency: {
dependency: 'الاعتماد',
table: 'الجدول', table: 'الجدول',
dependent_table: 'عرض الاعتمادات', dependent_table: 'عرض الاعتمادات',
delete_dependency: 'حذف', delete_dependency: 'حذف',
@@ -212,8 +206,8 @@ export const ar: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'لا توجد اعتمادات', title: 'لا توجد علاقات',
description: 'إنشاء اعتماد للبدء', description: 'إنشاء علاقة للبدء',
}, },
}, },
@@ -238,6 +232,33 @@ export const ar: LanguageTranslation = {
}, },
}, },
visuals_section: {
visuals: 'مرئيات',
tabs: {
areas: 'Areas',
notes: 'ملاحظات',
},
},
notes_section: {
filter: 'تصفية',
add_note: 'إضافة ملاحظة',
no_results: 'لم يتم العثور على ملاحظات',
clear: 'مسح التصفية',
empty_state: {
title: 'لا توجد ملاحظات',
description: 'أنشئ ملاحظة لإضافة تعليقات نصية على اللوحة',
},
note: {
empty_note: 'ملاحظة فارغة',
note_actions: {
title: 'إجراءات الملاحظة',
edit_content: 'تحرير المحتوى',
delete_note: 'حذف الملاحظة',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -254,6 +275,7 @@ export const ar: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'لم يتم تحديد قيم التعداد',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -276,7 +298,7 @@ export const ar: LanguageTranslation = {
show_all: 'عرض الكل', show_all: 'عرض الكل',
undo: 'تراجع', undo: 'تراجع',
redo: 'إعادة', redo: 'إعادة',
reorder_diagram: 'إعادة ترتيب الرسم البياني', reorder_diagram: 'ترتيب تلقائي للرسم البياني',
highlight_overlapping_tables: 'تمييز الجداول المتداخلة', highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
// TODO: Translate // TODO: Translate
filter: 'Filter Tables', filter: 'Filter Tables',
@@ -313,13 +335,13 @@ export const ar: LanguageTranslation = {
cancel: 'إلغاء', cancel: 'إلغاء',
import_from_file: 'استيراد من ملف', import_from_file: 'استيراد من ملف',
back: 'رجوع', back: 'رجوع',
empty_diagram: 'مخطط فارغ', empty_diagram: 'قاعدة بيانات فارغة',
continue: 'متابعة', continue: 'متابعة',
import: 'استيراد', import: 'استيراد',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'فتح مخطط', title: 'فتح قاعدة بيانات',
description: 'اختر مخططًا لفتحه من القائمة ادناه', description: 'اختر مخططًا لفتحه من القائمة ادناه',
table_columns: { table_columns: {
name: 'الإسم', name: 'الإسم',
@@ -329,6 +351,12 @@ export const ar: LanguageTranslation = {
}, },
cancel: 'إلغاء', cancel: 'إلغاء',
open: 'فتح', open: 'فتح',
diagram_actions: {
open: 'فتح',
duplicate: 'تكرار',
delete: 'حذف',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -474,9 +502,11 @@ export const ar: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'جدول جديد', new_table: 'جدول جديد',
new_view: 'عرض جديد',
new_relationship: 'علاقة جديدة', new_relationship: 'علاقة جديدة',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'ملاحظة جديدة',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -495,6 +525,8 @@ export const ar: LanguageTranslation = {
language_select: { language_select: {
change_language: 'اللغة', change_language: 'اللغة',
}, },
on: 'تشغيل',
off: 'إيقاف',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const bn: LanguageTranslation = { export const bn: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'নতুন',
browse: 'ব্রাউজ',
tables: 'টেবিল',
refs: 'রেফস',
dependencies: 'নির্ভরতা',
custom_types: 'কাস্টম টাইপ',
visuals: 'ভিজ্যুয়াল',
},
menu: { menu: {
file: { actions: {
file: 'ফাইল', actions: 'কার্য',
new: 'নতুন', new: 'নতুন...',
open: 'খুলুন', browse: 'ব্রাউজ করুন...',
save: 'সংরক্ষণ করুন', save: 'সংরক্ষণ করুন',
import: 'ডাটাবেস আমদানি করুন', import: 'ডাটাবেস আমদানি করুন',
export_sql: 'SQL রপ্তানি করুন', export_sql: 'SQL রপ্তানি করুন',
export_as: 'রূপে রপ্তানি করুন', export_as: 'রূপে রপ্তানি করুন',
delete_diagram: 'ডায়াগ্রাম মুছুন', delete_diagram: 'মুছুন',
exit: 'প্রস্থান করুন',
}, },
edit: { edit: {
edit: 'সম্পাদনা', edit: 'সম্পাদনা',
@@ -29,6 +37,7 @@ export const bn: LanguageTranslation = {
hide_field_attributes: 'ফিল্ড অ্যাট্রিবিউট লুকান', hide_field_attributes: 'ফিল্ড অ্যাট্রিবিউট লুকান',
show_field_attributes: 'ফিল্ড অ্যাট্রিবিউট দেখান', show_field_attributes: 'ফিল্ড অ্যাট্রিবিউট দেখান',
zoom_on_scroll: 'স্ক্রলে জুম করুন', zoom_on_scroll: 'স্ক্রলে জুম করুন',
show_views: 'ডাটাবেস ভিউ',
theme: 'থিম', theme: 'থিম',
show_dependencies: 'নির্ভরতাগুলি দেখান', show_dependencies: 'নির্ভরতাগুলি দেখান',
hide_dependencies: 'নির্ভরতাগুলি লুকান', hide_dependencies: 'নির্ভরতাগুলি লুকান',
@@ -66,22 +75,13 @@ export const bn: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'ডায়াগ্রাম পুনর্বিন্যাস করুন', title: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
description: description:
'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?', 'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?',
reorder: 'পুনর্বিন্যাস করুন', reorder: 'স্বয়ংক্রিয় সাজান',
cancel: 'বাতিল করুন', cancel: 'বাতিল করুন',
}, },
multiple_schemas_alert: {
title: 'বহু স্কিমা',
description:
'{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
// TODO: Translate
show_me: 'Show me',
none: 'কিছুই না',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'কপি ব্যর্থ হয়েছে', title: 'কপি ব্যর্থ হয়েছে',
@@ -116,14 +116,11 @@ export const bn: LanguageTranslation = {
copied: 'অনুলিপি সম্পন্ন!', copied: 'অনুলিপি সম্পন্ন!',
side_panel: { side_panel: {
schema: 'স্কিমা:',
filter_by_schema: 'স্কিমা দ্বারা ফিল্টার করুন',
search_schema: 'স্কিমা খুঁজুন...',
no_schemas_found: 'কোনো স্কিমা পাওয়া যায়নি।',
view_all_options: 'সমস্ত বিকল্প দেখুন...', view_all_options: 'সমস্ত বিকল্প দেখুন...',
tables_section: { tables_section: {
tables: 'টেবিল', tables: 'টেবিল',
add_table: 'টেবিল যোগ করুন', add_table: 'টেবিল যোগ করুন',
add_view: 'ভিউ যোগ করুন',
filter: 'ফিল্টার', filter: 'ফিল্টার',
collapse: 'সব ভাঁজ করুন', collapse: 'সব ভাঁজ করুন',
// TODO: Translate // TODO: Translate
@@ -149,6 +146,7 @@ export const bn: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ফিল্ড কর্ম', title: 'ফিল্ড কর্ম',
unique: 'অদ্বিতীয়', unique: 'অদ্বিতীয়',
auto_increment: 'স্বয়ংক্রিয় বৃদ্ধি',
comments: 'মন্তব্য', comments: 'মন্তব্য',
no_comments: 'কোনো মন্তব্য নেই', no_comments: 'কোনো মন্তব্য নেই',
delete_field: 'ফিল্ড মুছুন', delete_field: 'ফিল্ড মুছুন',
@@ -164,6 +162,7 @@ export const bn: LanguageTranslation = {
title: 'ইনডেক্স কর্ম', title: 'ইনডেক্স কর্ম',
name: 'নাম', name: 'নাম',
unique: 'অদ্বিতীয়', unique: 'অদ্বিতীয়',
index_type: 'ইনডেক্স ধরন',
delete_index: 'ইনডেক্স মুছুন', delete_index: 'ইনডেক্স মুছুন',
}, },
table_actions: { table_actions: {
@@ -180,14 +179,17 @@ export const bn: LanguageTranslation = {
description: 'শুরু করতে একটি টেবিল তৈরি করুন', description: 'শুরু করতে একটি টেবিল তৈরি করুন',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'সম্পর্ক', refs: 'রেফস',
filter: 'ফিল্টার', filter: 'ফিল্টার',
add_relationship: 'সম্পর্ক যোগ করুন',
collapse: 'সব ভাঁজ করুন', collapse: 'সব ভাঁজ করুন',
add_relationship: 'সম্পর্ক যোগ করুন',
relationships: 'সম্পর্ক',
dependencies: 'নির্ভরতাগুলি',
relationship: { relationship: {
relationship: 'সম্পর্ক',
primary: 'প্রাথমিক টেবিল', primary: 'প্রাথমিক টেবিল',
foreign: 'বিদেশি টেবিল', foreign: 'রেফারেন্স করা টেবিল',
cardinality: 'কার্ডিনালিটি', cardinality: 'কার্ডিনালিটি',
delete_relationship: 'মুছুন', delete_relationship: 'মুছুন',
relationship_actions: { relationship_actions: {
@@ -195,27 +197,19 @@ export const bn: LanguageTranslation = {
delete_relationship: 'মুছুন', delete_relationship: 'মুছুন',
}, },
}, },
empty_state: {
title: 'কোনো সম্পর্ক নেই',
description: 'টেবিল সংযোগ করতে একটি সম্পর্ক তৈরি করুন',
},
},
dependencies_section: {
dependencies: 'নির্ভরতাগুলি',
filter: 'ফিল্টার',
collapse: 'ভাঁজ করুন',
dependency: { dependency: {
dependency: 'নির্ভরতা',
table: 'টেবিল', table: 'টেবিল',
dependent_table: 'নির্ভরশীল টেবিল', dependent_table: 'নির্ভরশীল ভিউ',
delete_dependency: 'নির্ভরতা মুছুন', delete_dependency: 'মুছুন',
dependency_actions: { dependency_actions: {
title: 'কর্ম', title: 'কর্ম',
delete_dependency: 'নির্ভরতা মুছুন', delete_dependency: 'মুছুন',
}, },
}, },
empty_state: { empty_state: {
title: 'কোনো নির্ভরতাগুলি নেই', title: 'কোনো সম্পর্ক নেই',
description: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।', description: 'শুরু করতে একটি সম্পর্ক তৈরি করুন',
}, },
}, },
@@ -239,6 +233,35 @@ export const bn: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'ভিজ্যুয়াল',
tabs: {
areas: 'Areas',
notes: 'নোট',
},
},
notes_section: {
filter: 'ফিল্টার',
add_note: 'নোট যোগ করুন',
no_results: 'কোনো নোট পাওয়া যায়নি',
clear: 'ফিল্টার সাফ করুন',
empty_state: {
title: 'কোনো নোট নেই',
description:
'ক্যানভাসে টেক্সট টীকা যোগ করতে একটি নোট তৈরি করুন',
},
note: {
empty_note: 'খালি নোট',
note_actions: {
title: 'নোট ক্রিয়া',
edit_content: 'বিষয়বস্তু সম্পাদনা',
delete_note: 'নোট মুছুন',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -255,6 +278,7 @@ export const bn: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'কোন enum মান সংজ্ঞায়িত নেই',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -277,7 +301,7 @@ export const bn: LanguageTranslation = {
show_all: 'সব দেখান', show_all: 'সব দেখান',
undo: 'পূর্বাবস্থায় ফিরুন', undo: 'পূর্বাবস্থায় ফিরুন',
redo: 'পুনরায় করুন', redo: 'পুনরায় করুন',
reorder_diagram: 'ডায়াগ্রাম পুনর্বিন্যাস করুন', reorder_diagram: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন', highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন',
// TODO: Translate // TODO: Translate
@@ -315,13 +339,13 @@ export const bn: LanguageTranslation = {
cancel: 'বাতিল করুন', cancel: 'বাতিল করুন',
back: 'ফিরে যান', back: 'ফিরে যান',
import_from_file: 'ফাইল থেকে আমদানি করুন', import_from_file: 'ফাইল থেকে আমদানি করুন',
empty_diagram: 'ফাঁকা চিত্র', empty_diagram: 'খালি ডাটাবেস',
continue: 'চালিয়ে যান', continue: 'চালিয়ে যান',
import: 'আমদানি করুন', import: 'আমদানি করুন',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'চিত্র খুলুন', title: 'ডেটাবেস খুলুন',
description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।', description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।',
table_columns: { table_columns: {
name: 'নাম', name: 'নাম',
@@ -331,6 +355,12 @@ export const bn: LanguageTranslation = {
}, },
cancel: 'বাতিল করুন', cancel: 'বাতিল করুন',
open: 'খুলুন', open: 'খুলুন',
diagram_actions: {
open: 'খুলুন',
duplicate: 'ডুপ্লিকেট',
delete: 'মুছুন',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -479,9 +509,11 @@ export const bn: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'নতুন টেবিল', new_table: 'নতুন টেবিল',
new_view: 'নতুন ভিউ',
new_relationship: 'নতুন সম্পর্ক', new_relationship: 'নতুন সম্পর্ক',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'নতুন নোট',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -500,6 +532,9 @@ export const bn: LanguageTranslation = {
language_select: { language_select: {
change_language: 'ভাষা পরিবর্তন করুন', change_language: 'ভাষা পরিবর্তন করুন',
}, },
on: 'চালু',
off: 'বন্ধ',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const de: LanguageTranslation = { export const de: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Neu',
browse: 'Durchsuchen',
tables: 'Tabellen',
refs: 'Refs',
dependencies: 'Abhängigkeiten',
custom_types: 'Benutzerdefinierte Typen',
visuals: 'Darstellungen',
},
menu: { menu: {
file: { actions: {
file: 'Datei', actions: 'Aktionen',
new: 'Neu', new: 'Neu...',
open: 'Öffnen', browse: 'Durchsuchen...',
save: 'Speichern', save: 'Speichern',
import: 'Datenbank importieren', import: 'Datenbank importieren',
export_sql: 'SQL exportieren', export_sql: 'SQL exportieren',
export_as: 'Exportieren als', export_as: 'Exportieren als',
delete_diagram: 'Diagramm löschen', delete_diagram: 'Löschen',
exit: 'Beenden',
}, },
edit: { edit: {
edit: 'Bearbeiten', edit: 'Bearbeiten',
@@ -29,6 +37,7 @@ export const de: LanguageTranslation = {
hide_field_attributes: 'Feldattribute ausblenden', hide_field_attributes: 'Feldattribute ausblenden',
show_field_attributes: 'Feldattribute anzeigen', show_field_attributes: 'Feldattribute anzeigen',
zoom_on_scroll: 'Zoom beim Scrollen', zoom_on_scroll: 'Zoom beim Scrollen',
show_views: 'Datenbankansichten',
theme: 'Stil', theme: 'Stil',
show_dependencies: 'Abhängigkeiten anzeigen', show_dependencies: 'Abhängigkeiten anzeigen',
hide_dependencies: 'Abhängigkeiten ausblenden', hide_dependencies: 'Abhängigkeiten ausblenden',
@@ -66,22 +75,13 @@ export const de: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Diagramm neu anordnen', title: 'Diagramm automatisch anordnen',
description: description:
'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?', 'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?',
reorder: 'Neu anordnen', reorder: 'Automatisch anordnen',
cancel: 'Abbrechen', cancel: 'Abbrechen',
}, },
multiple_schemas_alert: {
title: 'Mehrere Schemas',
description:
'{{schemasCount}} Schemas in diesem Diagramm. Derzeit angezeigt: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'Keine',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Kopieren fehlgeschlagen', title: 'Kopieren fehlgeschlagen',
@@ -117,14 +117,11 @@ export const de: LanguageTranslation = {
copied: 'Kopiert!', copied: 'Kopiert!',
side_panel: { side_panel: {
schema: 'Schema:',
filter_by_schema: 'Nach Schema filtern',
search_schema: 'Schema suchen...',
no_schemas_found: 'Keine Schemas gefunden.',
view_all_options: 'Alle Optionen anzeigen...', view_all_options: 'Alle Optionen anzeigen...',
tables_section: { tables_section: {
tables: 'Tabellen', tables: 'Tabellen',
add_table: 'Tabelle hinzufügen', add_table: 'Tabelle hinzufügen',
add_view: 'Ansicht hinzufügen',
filter: 'Filter', filter: 'Filter',
collapse: 'Alle einklappen', collapse: 'Alle einklappen',
// TODO: Translate // TODO: Translate
@@ -150,6 +147,7 @@ export const de: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Feldattribute', title: 'Feldattribute',
unique: 'Eindeutig', unique: 'Eindeutig',
auto_increment: 'Automatisch hochzählen',
comments: 'Kommentare', comments: 'Kommentare',
no_comments: 'Keine Kommentare', no_comments: 'Keine Kommentare',
delete_field: 'Feld löschen', delete_field: 'Feld löschen',
@@ -165,6 +163,7 @@ export const de: LanguageTranslation = {
title: 'Indexattribute', title: 'Indexattribute',
name: 'Name', name: 'Name',
unique: 'Eindeutig', unique: 'Eindeutig',
index_type: 'Indextyp',
delete_index: 'Index löschen', delete_index: 'Index löschen',
}, },
table_actions: { table_actions: {
@@ -181,32 +180,26 @@ export const de: LanguageTranslation = {
description: 'Erstellen Sie eine Tabelle, um zu beginnen', description: 'Erstellen Sie eine Tabelle, um zu beginnen',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Beziehungen', refs: 'Refs',
filter: 'Filter', filter: 'Filter',
add_relationship: 'Beziehung hinzufügen',
collapse: 'Alle einklappen', collapse: 'Alle einklappen',
add_relationship: 'Beziehung hinzufügen',
relationships: 'Beziehungen',
dependencies: 'Abhängigkeiten',
relationship: { relationship: {
relationship: 'Beziehung',
primary: 'Primäre Tabelle', primary: 'Primäre Tabelle',
foreign: 'Referenzierte Tabelle', foreign: 'Referenzierte Tabelle',
cardinality: 'Kardinalität', cardinality: 'Kardinalität',
delete_relationship: 'Beziehung löschen', delete_relationship: 'Löschen',
relationship_actions: { relationship_actions: {
title: 'Aktionen', title: 'Aktionen',
delete_relationship: 'Beziehung löschen', delete_relationship: 'Löschen',
}, },
}, },
empty_state: {
title: 'Keine Beziehungen',
description:
'Erstellen Sie eine Beziehung, um Tabellen zu verbinden',
},
},
dependencies_section: {
dependencies: 'Abhängigkeiten',
filter: 'Filter',
collapse: 'Alle einklappen',
dependency: { dependency: {
dependency: 'Abhängigkeit',
table: 'Tabelle', table: 'Tabelle',
dependent_table: 'Abhängige Ansicht', dependent_table: 'Abhängige Ansicht',
delete_dependency: 'Löschen', delete_dependency: 'Löschen',
@@ -216,8 +209,8 @@ export const de: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Keine Abhängigkeiten', title: 'Keine Beziehungen',
description: 'Erstellen Sie eine Ansicht, um zu beginnen', description: 'Erstellen Sie eine Beziehung, um zu beginnen',
}, },
}, },
@@ -241,6 +234,35 @@ export const de: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Darstellungen',
tabs: {
areas: 'Areas',
notes: 'Notizen',
},
},
notes_section: {
filter: 'Filter',
add_note: 'Notiz hinzufügen',
no_results: 'Keine Notizen gefunden',
clear: 'Filter löschen',
empty_state: {
title: 'Keine Notizen',
description:
'Erstellen Sie eine Notiz, um Textanmerkungen auf der Leinwand hinzuzufügen',
},
note: {
empty_note: 'Leere Notiz',
note_actions: {
title: 'Notiz-Aktionen',
edit_content: 'Inhalt bearbeiten',
delete_note: 'Notiz löschen',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -257,6 +279,7 @@ export const de: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Keine Enum-Werte definiert',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -279,7 +302,7 @@ export const de: LanguageTranslation = {
show_all: 'Alle anzeigen', show_all: 'Alle anzeigen',
undo: 'Rückgängig', undo: 'Rückgängig',
redo: 'Wiederholen', redo: 'Wiederholen',
reorder_diagram: 'Diagramm neu anordnen', reorder_diagram: 'Diagramm automatisch anordnen',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
@@ -319,13 +342,13 @@ export const de: LanguageTranslation = {
back: 'Zurück', back: 'Zurück',
// TODO: Translate // TODO: Translate
import_from_file: 'Import from File', import_from_file: 'Import from File',
empty_diagram: 'Leeres Diagramm', empty_diagram: 'Leere Datenbank',
continue: 'Weiter', continue: 'Weiter',
import: 'Importieren', import: 'Importieren',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Diagramm öffnen', title: 'Datenbank öffnen',
description: 'Wählen Sie ein Diagramm aus der Liste unten aus.', description: 'Wählen Sie ein Diagramm aus der Liste unten aus.',
table_columns: { table_columns: {
name: 'Name', name: 'Name',
@@ -335,6 +358,12 @@ export const de: LanguageTranslation = {
}, },
cancel: 'Abbrechen', cancel: 'Abbrechen',
open: 'Öffnen', open: 'Öffnen',
diagram_actions: {
open: 'Öffnen',
duplicate: 'Duplizieren',
delete: 'Löschen',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -483,9 +512,11 @@ export const de: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Neue Tabelle', new_table: 'Neue Tabelle',
new_view: 'Neue Ansicht',
new_relationship: 'Neue Beziehung', new_relationship: 'Neue Beziehung',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Neue Notiz',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -505,6 +536,9 @@ export const de: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Sprache', change_language: 'Sprache',
}, },
on: 'Ein',
off: 'Aus',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata } from '../types';
export const en = { export const en = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'New',
browse: 'Browse',
tables: 'Tables',
refs: 'Refs',
dependencies: 'Dependencies',
custom_types: 'Custom Types',
visuals: 'Visuals',
},
menu: { menu: {
file: { actions: {
file: 'File', actions: 'Actions',
new: 'New', new: 'New...',
open: 'Open', browse: 'Browse...',
save: 'Save', save: 'Save',
import: 'Import', import: 'Import',
export_sql: 'Export SQL', export_sql: 'Export SQL',
export_as: 'Export as', export_as: 'Export as',
delete_diagram: 'Delete Diagram', delete_diagram: 'Delete',
exit: 'Exit',
}, },
edit: { edit: {
edit: 'Edit', edit: 'Edit',
@@ -29,6 +37,7 @@ export const en = {
hide_field_attributes: 'Hide Field Attributes', hide_field_attributes: 'Hide Field Attributes',
show_field_attributes: 'Show Field Attributes', show_field_attributes: 'Show Field Attributes',
zoom_on_scroll: 'Zoom on Scroll', zoom_on_scroll: 'Zoom on Scroll',
show_views: 'Database Views',
theme: 'Theme', theme: 'Theme',
show_dependencies: 'Show Dependencies', show_dependencies: 'Show Dependencies',
hide_dependencies: 'Hide Dependencies', hide_dependencies: 'Hide Dependencies',
@@ -64,21 +73,13 @@ export const en = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Reorder Diagram', title: 'Auto Arrange Diagram',
description: description:
'This action will rearrange all tables in the diagram. Do you want to continue?', 'This action will rearrange all tables in the diagram. Do you want to continue?',
reorder: 'Reorder', reorder: 'Auto Arrange',
cancel: 'Cancel', cancel: 'Cancel',
}, },
multiple_schemas_alert: {
title: 'Multiple Schemas',
description:
'{{schemasCount}} schemas in this diagram. Currently displaying: {{formattedSchemas}}.',
show_me: 'Show me',
none: 'none',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Copy failed', title: 'Copy failed',
@@ -113,14 +114,11 @@ export const en = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'Schema:',
filter_by_schema: 'Filter by schema',
search_schema: 'Search schema...',
no_schemas_found: 'No schemas found.',
view_all_options: 'View all Options...', view_all_options: 'View all Options...',
tables_section: { tables_section: {
tables: 'Tables', tables: 'Tables',
add_table: 'Add Table', add_table: 'Add Table',
add_view: 'Add View',
filter: 'Filter', filter: 'Filter',
collapse: 'Collapse All', collapse: 'Collapse All',
clear: 'Clear Filter', clear: 'Clear Filter',
@@ -144,6 +142,7 @@ export const en = {
field_actions: { field_actions: {
title: 'Field Attributes', title: 'Field Attributes',
unique: 'Unique', unique: 'Unique',
auto_increment: 'Auto Increment',
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Precision', precision: 'Precision',
scale: 'Scale', scale: 'Scale',
@@ -157,6 +156,7 @@ export const en = {
title: 'Index Attributes', title: 'Index Attributes',
name: 'Name', name: 'Name',
unique: 'Unique', unique: 'Unique',
index_type: 'Index Type',
delete_index: 'Delete Index', delete_index: 'Delete Index',
}, },
table_actions: { table_actions: {
@@ -173,12 +173,15 @@ export const en = {
description: 'Create a table to get started', description: 'Create a table to get started',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relationships', refs: 'Refs',
filter: 'Filter', filter: 'Filter',
add_relationship: 'Add Relationship',
collapse: 'Collapse All', collapse: 'Collapse All',
add_relationship: 'Add Relationship',
relationships: 'Relationships',
dependencies: 'Dependencies',
relationship: { relationship: {
relationship: 'Relationship',
primary: 'Primary Table', primary: 'Primary Table',
foreign: 'Referenced Table', foreign: 'Referenced Table',
cardinality: 'Cardinality', cardinality: 'Cardinality',
@@ -188,16 +191,8 @@ export const en = {
delete_relationship: 'Delete', delete_relationship: 'Delete',
}, },
}, },
empty_state: {
title: 'No relationships',
description: 'Create a relationship to connect tables',
},
},
dependencies_section: {
dependencies: 'Dependencies',
filter: 'Filter',
collapse: 'Collapse All',
dependency: { dependency: {
dependency: 'Dependency',
table: 'Table', table: 'Table',
dependent_table: 'Dependent View', dependent_table: 'Dependent View',
delete_dependency: 'Delete', delete_dependency: 'Delete',
@@ -207,8 +202,8 @@ export const en = {
}, },
}, },
empty_state: { empty_state: {
title: 'No dependencies', title: 'No relationships',
description: 'Create a view to get started', description: 'Create a relationship to get started',
}, },
}, },
@@ -232,6 +227,34 @@ export const en = {
}, },
}, },
visuals_section: {
visuals: 'Visuals',
tabs: {
areas: 'Areas',
notes: 'Notes',
},
},
notes_section: {
filter: 'Filter',
add_note: 'Add Note',
no_results: 'No notes found',
clear: 'Clear Filter',
empty_state: {
title: 'No Notes',
description:
'Create a note to add text annotations on the canvas',
},
note: {
empty_note: 'Empty note',
note_actions: {
title: 'Note Actions',
edit_content: 'Edit Content',
delete_note: 'Delete Note',
},
},
},
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
filter: 'Filter', filter: 'Filter',
@@ -247,6 +270,7 @@ export const en = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'No enum values defined',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -269,7 +293,7 @@ export const en = {
show_all: 'Show All', show_all: 'Show All',
undo: 'Undo', undo: 'Undo',
redo: 'Redo', redo: 'Redo',
reorder_diagram: 'Reorder Diagram', reorder_diagram: 'Auto Arrange Diagram',
highlight_overlapping_tables: 'Highlight Overlapping Tables', highlight_overlapping_tables: 'Highlight Overlapping Tables',
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -305,13 +329,13 @@ export const en = {
cancel: 'Cancel', cancel: 'Cancel',
import_from_file: 'Import from File', import_from_file: 'Import from File',
back: 'Back', back: 'Back',
empty_diagram: 'Empty diagram', empty_diagram: 'Empty database',
continue: 'Continue', continue: 'Continue',
import: 'Import', import: 'Import',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Open Diagram', title: 'Open Database',
description: 'Select a diagram to open from the list below.', description: 'Select a diagram to open from the list below.',
table_columns: { table_columns: {
name: 'Name', name: 'Name',
@@ -321,6 +345,12 @@ export const en = {
}, },
cancel: 'Cancel', cancel: 'Cancel',
open: 'Open', open: 'Open',
diagram_actions: {
open: 'Open',
duplicate: 'Duplicate',
delete: 'Delete',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -468,8 +498,10 @@ export const en = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'New Table', new_table: 'New Table',
new_view: 'New View',
new_relationship: 'New Relationship', new_relationship: 'New Relationship',
new_area: 'New Area', new_area: 'New Area',
new_note: 'New Note',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -488,6 +520,9 @@ export const en = {
language_select: { language_select: {
change_language: 'Language', change_language: 'Language',
}, },
on: 'On',
off: 'Off',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const es: LanguageTranslation = { export const es: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Nuevo',
browse: 'Examinar',
tables: 'Tablas',
refs: 'Refs',
dependencies: 'Dependencias',
custom_types: 'Tipos Personalizados',
visuals: 'Visuales',
},
menu: { menu: {
file: { actions: {
file: 'Archivo', actions: 'Acciones',
new: 'Nuevo', new: 'Nuevo...',
open: 'Abrir', browse: 'Examinar...',
save: 'Guardar', save: 'Guardar',
import: 'Importar Base de Datos', import: 'Importar Base de Datos',
export_sql: 'Exportar SQL', export_sql: 'Exportar SQL',
export_as: 'Exportar como', export_as: 'Exportar como',
delete_diagram: 'Eliminar Diagrama', delete_diagram: 'Eliminar',
exit: 'Salir',
}, },
edit: { edit: {
edit: 'Editar', edit: 'Editar',
@@ -29,6 +37,7 @@ export const es: LanguageTranslation = {
show_sidebar: 'Mostrar Barra Lateral', show_sidebar: 'Mostrar Barra Lateral',
hide_sidebar: 'Ocultar Barra Lateral', hide_sidebar: 'Ocultar Barra Lateral',
zoom_on_scroll: 'Zoom al Desplazarse', zoom_on_scroll: 'Zoom al Desplazarse',
show_views: 'Vistas de Base de Datos',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Mostrar dependencias', show_dependencies: 'Mostrar dependencias',
hide_dependencies: 'Ocultar dependencias', hide_dependencies: 'Ocultar dependencias',
@@ -65,10 +74,10 @@ export const es: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Reordenar Diagrama', title: 'Organizar Diagrama Automáticamente',
description: description:
'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?', 'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?',
reorder: 'Reordenar', reorder: 'Organizar Automáticamente',
cancel: 'Cancelar', cancel: 'Cancelar',
}, },
@@ -106,14 +115,11 @@ export const es: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'Esquema:',
filter_by_schema: 'Filtrar por esquema',
search_schema: 'Buscar esquema...',
no_schemas_found: 'No se encontraron esquemas.',
view_all_options: 'Ver todas las opciones...', view_all_options: 'Ver todas las opciones...',
tables_section: { tables_section: {
tables: 'Tablas', tables: 'Tablas',
add_table: 'Agregar Tabla', add_table: 'Agregar Tabla',
add_view: 'Agregar Vista',
filter: 'Filtrar', filter: 'Filtrar',
collapse: 'Colapsar Todo', collapse: 'Colapsar Todo',
// TODO: Translate // TODO: Translate
@@ -139,6 +145,7 @@ export const es: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Atributos del Campo', title: 'Atributos del Campo',
unique: 'Único', unique: 'Único',
auto_increment: 'Autoincremento',
comments: 'Comentarios', comments: 'Comentarios',
no_comments: 'Sin comentarios', no_comments: 'Sin comentarios',
delete_field: 'Eliminar Campo', delete_field: 'Eliminar Campo',
@@ -154,6 +161,7 @@ export const es: LanguageTranslation = {
title: 'Atributos del Índice', title: 'Atributos del Índice',
name: 'Nombre', name: 'Nombre',
unique: 'Único', unique: 'Único',
index_type: 'Tipo de Índice',
delete_index: 'Eliminar Índice', delete_index: 'Eliminar Índice',
}, },
table_actions: { table_actions: {
@@ -170,14 +178,17 @@ export const es: LanguageTranslation = {
description: 'Crea una tabla para comenzar', description: 'Crea una tabla para comenzar',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relaciones', refs: 'Refs',
add_relationship: 'Agregar Relación',
filter: 'Filtrar', filter: 'Filtrar',
collapse: 'Colapsar Todo', collapse: 'Colapsar Todo',
add_relationship: 'Agregar Relación',
relationships: 'Relaciones',
dependencies: 'Dependencias',
relationship: { relationship: {
primary: 'Primaria', relationship: 'Relación',
foreign: 'Foránea', primary: 'Tabla Primaria',
foreign: 'Tabla Referenciada',
cardinality: 'Cardinalidad', cardinality: 'Cardinalidad',
delete_relationship: 'Eliminar', delete_relationship: 'Eliminar',
relationship_actions: { relationship_actions: {
@@ -185,18 +196,10 @@ export const es: LanguageTranslation = {
delete_relationship: 'Eliminar', delete_relationship: 'Eliminar',
}, },
}, },
empty_state: {
title: 'No hay relaciones',
description: 'Crea una relación para conectar tablas',
},
},
dependencies_section: {
dependencies: 'Dependencias',
filter: 'Filtro',
collapse: 'Colapsar todo',
dependency: { dependency: {
dependency: 'Dependencia',
table: 'Tabla', table: 'Tabla',
dependent_table: 'Vista dependiente', dependent_table: 'Vista Dependiente',
delete_dependency: 'Eliminar', delete_dependency: 'Eliminar',
dependency_actions: { dependency_actions: {
title: 'Acciones', title: 'Acciones',
@@ -204,8 +207,8 @@ export const es: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Sin dependencias', title: 'Sin relaciones',
description: 'Crea una vista para comenzar', description: 'Crea una relación para comenzar',
}, },
}, },
@@ -229,6 +232,35 @@ export const es: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuales',
tabs: {
areas: 'Areas',
notes: 'Notas',
},
},
notes_section: {
filter: 'Filtrar',
add_note: 'Agregar Nota',
no_results: 'No se encontraron notas',
clear: 'Limpiar Filtro',
empty_state: {
title: 'Sin Notas',
description:
'Crea una nota para agregar anotaciones de texto en el lienzo',
},
note: {
empty_note: 'Nota vacía',
note_actions: {
title: 'Acciones de Nota',
edit_content: 'Editar Contenido',
delete_note: 'Eliminar Nota',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -245,6 +277,7 @@ export const es: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'No hay valores de enum definidos',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -267,7 +300,7 @@ export const es: LanguageTranslation = {
show_all: 'Mostrar Todo', show_all: 'Mostrar Todo',
undo: 'Deshacer', undo: 'Deshacer',
redo: 'Rehacer', redo: 'Rehacer',
reorder_diagram: 'Reordenar Diagrama', reorder_diagram: 'Organizar Diagrama Automáticamente',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -306,13 +339,13 @@ export const es: LanguageTranslation = {
back: 'Atrás', back: 'Atrás',
// TODO: Translate // TODO: Translate
import_from_file: 'Import from File', import_from_file: 'Import from File',
empty_diagram: 'Diagrama vacío', empty_diagram: 'Base de datos vacía',
continue: 'Continuar', continue: 'Continuar',
import: 'Importar', import: 'Importar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Abrir Diagrama', title: 'Abrir Base de Datos',
description: description:
'Selecciona un diagrama para abrir de la lista a continuación.', 'Selecciona un diagrama para abrir de la lista a continuación.',
table_columns: { table_columns: {
@@ -323,6 +356,12 @@ export const es: LanguageTranslation = {
}, },
cancel: 'Cancelar', cancel: 'Cancelar',
open: 'Abrir', open: 'Abrir',
diagram_actions: {
open: 'Abrir',
duplicate: 'Duplicar',
delete: 'Eliminar',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -424,14 +463,6 @@ export const es: LanguageTranslation = {
confirm: '¡Claro!', confirm: '¡Claro!',
}, },
multiple_schemas_alert: {
title: 'Múltiples Esquemas',
description:
'{{schemasCount}} esquemas en este diagrama. Actualmente mostrando: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'nada',
},
// TODO: Translate // TODO: Translate
export_diagram_dialog: { export_diagram_dialog: {
title: 'Export Diagram', title: 'Export Diagram',
@@ -480,9 +511,11 @@ export const es: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Nueva Tabla', new_table: 'Nueva Tabla',
new_view: 'Nueva Vista',
new_relationship: 'Nueva Relación', new_relationship: 'Nueva Relación',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Nueva Nota',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -502,6 +535,9 @@ export const es: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Idioma', change_language: 'Idioma',
}, },
on: 'Encendido',
off: 'Apagado',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const fr: LanguageTranslation = { export const fr: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Nouveau',
browse: 'Parcourir',
tables: 'Tables',
refs: 'Refs',
dependencies: 'Dépendances',
custom_types: 'Types Personnalisés',
visuals: 'Visuels',
},
menu: { menu: {
file: { actions: {
file: 'Fichier', actions: 'Actions',
new: 'Nouveau', new: 'Nouveau...',
open: 'Ouvrir', browse: 'Parcourir...',
save: 'Enregistrer', save: 'Enregistrer',
import: 'Importer Base de Données', import: 'Importer Base de Données',
export_sql: 'Exporter SQL', export_sql: 'Exporter SQL',
export_as: 'Exporter en tant que', export_as: 'Exporter en tant que',
delete_diagram: 'Supprimer le Diagramme', delete_diagram: 'Supprimer',
exit: 'Quitter',
}, },
edit: { edit: {
edit: 'Édition', edit: 'Édition',
@@ -29,6 +37,7 @@ export const fr: LanguageTranslation = {
hide_field_attributes: 'Masquer les Attributs de Champ', hide_field_attributes: 'Masquer les Attributs de Champ',
show_field_attributes: 'Afficher les Attributs de Champ', show_field_attributes: 'Afficher les Attributs de Champ',
zoom_on_scroll: 'Zoom sur le Défilement', zoom_on_scroll: 'Zoom sur le Défilement',
show_views: 'Vues de Base de Données',
theme: 'Thème', theme: 'Thème',
show_dependencies: 'Afficher les Dépendances', show_dependencies: 'Afficher les Dépendances',
hide_dependencies: 'Masquer les Dépendances', hide_dependencies: 'Masquer les Dépendances',
@@ -64,10 +73,10 @@ export const fr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Réorganiser le Diagramme', title: 'Organiser Automatiquement le Diagramme',
description: description:
'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?', 'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?',
reorder: 'Réorganiser', reorder: 'Organiser Automatiquement',
cancel: 'Annuler', cancel: 'Annuler',
}, },
@@ -105,14 +114,11 @@ export const fr: LanguageTranslation = {
copied: 'Copié !', copied: 'Copié !',
side_panel: { side_panel: {
schema: 'Schéma:',
filter_by_schema: 'Filtrer par schéma',
search_schema: 'Rechercher un schéma...',
no_schemas_found: 'Aucun schéma trouvé.',
view_all_options: 'Voir toutes les Options...', view_all_options: 'Voir toutes les Options...',
tables_section: { tables_section: {
tables: 'Tables', tables: 'Tables',
add_table: 'Ajouter une Table', add_table: 'Ajouter une Table',
add_view: 'Ajouter une Vue',
filter: 'Filtrer', filter: 'Filtrer',
collapse: 'Réduire Tout', collapse: 'Réduire Tout',
clear: 'Effacer le Filtre', clear: 'Effacer le Filtre',
@@ -137,6 +143,7 @@ export const fr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Attributs du Champ', title: 'Attributs du Champ',
unique: 'Unique', unique: 'Unique',
auto_increment: 'Auto-incrément',
comments: 'Commentaires', comments: 'Commentaires',
no_comments: 'Pas de commentaires', no_comments: 'Pas de commentaires',
delete_field: 'Supprimer le Champ', delete_field: 'Supprimer le Champ',
@@ -152,6 +159,7 @@ export const fr: LanguageTranslation = {
title: "Attributs de l'Index", title: "Attributs de l'Index",
name: 'Nom', name: 'Nom',
unique: 'Unique', unique: 'Unique',
index_type: "Type d'index",
delete_index: "Supprimer l'Index", delete_index: "Supprimer l'Index",
}, },
table_actions: { table_actions: {
@@ -168,12 +176,15 @@ export const fr: LanguageTranslation = {
description: 'Créez une table pour commencer', description: 'Créez une table pour commencer',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relations', refs: 'Refs',
filter: 'Filtrer', filter: 'Filtrer',
add_relationship: 'Ajouter une Relation',
collapse: 'Réduire Tout', collapse: 'Réduire Tout',
add_relationship: 'Ajouter une Relation',
relationships: 'Relations',
dependencies: 'Dépendances',
relationship: { relationship: {
relationship: 'Relation',
primary: 'Table Principale', primary: 'Table Principale',
foreign: 'Table Référencée', foreign: 'Table Référencée',
cardinality: 'Cardinalité', cardinality: 'Cardinalité',
@@ -183,16 +194,8 @@ export const fr: LanguageTranslation = {
delete_relationship: 'Supprimer', delete_relationship: 'Supprimer',
}, },
}, },
empty_state: {
title: 'Aucune relation',
description: 'Créez une relation pour connecter les tables',
},
},
dependencies_section: {
dependencies: 'Dépendances',
filter: 'Filtrer',
collapse: 'Réduire Tout',
dependency: { dependency: {
dependency: 'Dépendance',
table: 'Table', table: 'Table',
dependent_table: 'Vue Dépendante', dependent_table: 'Vue Dépendante',
delete_dependency: 'Supprimer', delete_dependency: 'Supprimer',
@@ -202,8 +205,8 @@ export const fr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Aucune dépendance', title: 'Aucune relation',
description: 'Créez une vue pour commencer', description: 'Créez une relation pour commencer',
}, },
}, },
@@ -227,6 +230,35 @@ export const fr: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuels',
tabs: {
areas: 'Areas',
notes: 'Notes',
},
},
notes_section: {
filter: 'Filtrer',
add_note: 'Ajouter une Note',
no_results: 'Aucune note trouvée',
clear: 'Effacer le Filtre',
empty_state: {
title: 'Pas de Notes',
description:
'Créez une note pour ajouter des annotations de texte sur le canevas',
},
note: {
empty_note: 'Note vide',
note_actions: {
title: 'Actions de Note',
edit_content: 'Modifier le Contenu',
delete_note: 'Supprimer la Note',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -243,6 +275,7 @@ export const fr: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: "Aucune valeur d'énumération définie",
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -265,7 +298,7 @@ export const fr: LanguageTranslation = {
show_all: 'Afficher Tout', show_all: 'Afficher Tout',
undo: 'Annuler', undo: 'Annuler',
redo: 'Rétablir', redo: 'Rétablir',
reorder_diagram: 'Réorganiser le Diagramme', reorder_diagram: 'Organiser Automatiquement le Diagramme',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -303,13 +336,13 @@ export const fr: LanguageTranslation = {
cancel: 'Annuler', cancel: 'Annuler',
back: 'Retour', back: 'Retour',
import_from_file: "Importer à partir d'un fichier", import_from_file: "Importer à partir d'un fichier",
empty_diagram: 'Diagramme vide', empty_diagram: 'Base de données vide',
continue: 'Continuer', continue: 'Continuer',
import: 'Importer', import: 'Importer',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Ouvrir Diagramme', title: 'Ouvrir Base de Données',
description: description:
'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.', 'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.',
table_columns: { table_columns: {
@@ -320,6 +353,12 @@ export const fr: LanguageTranslation = {
}, },
cancel: 'Annuler', cancel: 'Annuler',
open: 'Ouvrir', open: 'Ouvrir',
diagram_actions: {
open: 'Ouvrir',
duplicate: 'Dupliquer',
delete: 'Supprimer',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -357,15 +396,6 @@ export const fr: LanguageTranslation = {
transparent_description: 'Remove background color from image.', transparent_description: 'Remove background color from image.',
}, },
multiple_schemas_alert: {
title: 'Schémas Multiples',
description:
'{{schemasCount}} schémas dans ce diagramme. Actuellement affiché(s) : {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'Aucun',
},
new_table_schema_dialog: { new_table_schema_dialog: {
title: 'Sélectionner un Schéma', title: 'Sélectionner un Schéma',
description: description:
@@ -477,9 +507,11 @@ export const fr: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Nouvelle Table', new_table: 'Nouvelle Table',
new_view: 'Nouvelle Vue',
new_relationship: 'Nouvelle Relation', new_relationship: 'Nouvelle Relation',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Nouvelle Note',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -499,6 +531,9 @@ export const fr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Langue', change_language: 'Langue',
}, },
on: 'Activé',
off: 'Désactivé',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const gu: LanguageTranslation = { export const gu: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'નવું',
browse: 'બ્રાઉજ',
tables: 'ટેબલો',
refs: 'રેફ્સ',
dependencies: 'નિર્ભરતાઓ',
custom_types: 'કસ્ટમ ટાઇપ',
visuals: 'Visuals',
},
menu: { menu: {
file: { actions: {
file: 'ફાઇલ', actions: 'ક્રિયાઓ',
new: 'નવું', new: 'નવું...',
open: 'ખોલો', browse: 'બ્રાઉજ કરો...',
save: 'સાચવો', save: 'સાચવો',
import: 'ડેટાબેસ આયાત કરો', import: 'ડેટાબેસ આયાત કરો',
export_sql: 'SQL નિકાસ કરો', export_sql: 'SQL નિકાસ કરો',
export_as: 'રૂપે નિકાસ કરો', export_as: 'રૂપે નિકાસ કરો',
delete_diagram: 'ડાયાગ્રામ કાઢી નાખો', delete_diagram: 'કાઢી નાખો',
exit: 'બહાર જાઓ',
}, },
edit: { edit: {
edit: 'ફેરફાર', edit: 'ફેરફાર',
@@ -29,6 +37,7 @@ export const gu: LanguageTranslation = {
hide_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ છુપાવો', hide_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ છુપાવો',
show_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ બતાવો', show_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ બતાવો',
zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો', zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો',
show_views: 'ડેટાબેઝ વ્યૂઝ',
theme: 'થિમ', theme: 'થિમ',
show_dependencies: 'નિર્ભરતાઓ બતાવો', show_dependencies: 'નિર્ભરતાઓ બતાવો',
hide_dependencies: 'નિર્ભરતાઓ છુપાવો', hide_dependencies: 'નિર્ભરતાઓ છુપાવો',
@@ -66,22 +75,13 @@ export const gu: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'ડાયાગ્રામ ફરી વ્યવસ્થિત કરો', title: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
description: description:
'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?', 'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?',
reorder: 'ફરી વ્યવસ્થિત કરો', reorder: 'ઑટોમેટિક ગોઠવો',
cancel: 'રદ કરો', cancel: 'રદ કરો',
}, },
multiple_schemas_alert: {
title: 'કઈંક વધારે સ્કીમા',
description:
'{{schemasCount}} સ્કીમા આ ડાયાગ્રામમાં છે. હાલમાં દર્શાવેલ છે: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'કઈ નહીં',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'નકલ નિષ્ફળ', title: 'નકલ નિષ્ફળ',
@@ -116,14 +116,11 @@ export const gu: LanguageTranslation = {
copied: 'નકલ થયું!', copied: 'નકલ થયું!',
side_panel: { side_panel: {
schema: 'સ્કીમા:',
filter_by_schema: 'સ્કીમા દ્વારા ફિલ્ટર કરો',
search_schema: 'સ્કીમા શોધો...',
no_schemas_found: 'કોઈ સ્કીમા મળ્યા નથી.',
view_all_options: 'બધા વિકલ્પો જુઓ...', view_all_options: 'બધા વિકલ્પો જુઓ...',
tables_section: { tables_section: {
tables: 'ટેબલ્સ', tables: 'ટેબલ્સ',
add_table: 'ટેબલ ઉમેરો', add_table: 'ટેબલ ઉમેરો',
add_view: 'વ્યૂ ઉમેરો',
filter: 'ફિલ્ટર', filter: 'ફિલ્ટર',
collapse: 'બધાને સકુચિત કરો', collapse: 'બધાને સકુચિત કરો',
// TODO: Translate // TODO: Translate
@@ -150,6 +147,7 @@ export const gu: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ફીલ્ડ લક્ષણો', title: 'ફીલ્ડ લક્ષણો',
unique: 'અદ્વિતીય', unique: 'અદ્વિતીય',
auto_increment: 'ઑટો ઇન્ક્રિમેન્ટ',
comments: 'ટિપ્પણીઓ', comments: 'ટિપ્પણીઓ',
no_comments: 'કોઈ ટિપ્પણીઓ નથી', no_comments: 'કોઈ ટિપ્પણીઓ નથી',
delete_field: 'ફીલ્ડ કાઢી નાખો', delete_field: 'ફીલ્ડ કાઢી નાખો',
@@ -165,6 +163,7 @@ export const gu: LanguageTranslation = {
title: 'ઇન્ડેક્સ લક્ષણો', title: 'ઇન્ડેક્સ લક્ષણો',
name: 'નામ', name: 'નામ',
unique: 'અદ્વિતીય', unique: 'અદ્વિતીય',
index_type: 'ઇન્ડેક્સ પ્રકાર',
delete_index: 'ઇન્ડેક્સ કાઢી નાખો', delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
}, },
table_actions: { table_actions: {
@@ -181,14 +180,17 @@ export const gu: LanguageTranslation = {
description: 'શરૂ કરવા માટે એક ટેબલ બનાવો', description: 'શરૂ કરવા માટે એક ટેબલ બનાવો',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'સંબંધો', refs: 'રેફ્સ',
filter: 'ફિલ્ટર', filter: 'ફિલ્ટર',
add_relationship: 'સંબંધ ઉમેરો',
collapse: 'બધાને સકુચિત કરો', collapse: 'બધાને સકુચિત કરો',
add_relationship: 'સંબંધ ઉમેરો',
relationships: 'સંબંધો',
dependencies: 'નિર્ભરતાઓ',
relationship: { relationship: {
relationship: 'સંબંધ',
primary: 'પ્રાથમિક ટેબલ', primary: 'પ્રાથમિક ટેબલ',
foreign: 'સંદર્ભ ટેબલ', foreign: 'સંદર્ભિત ટેબલ',
cardinality: 'કાર્ડિનાલિટી', cardinality: 'કાર્ડિનાલિટી',
delete_relationship: 'કાઢી નાખો', delete_relationship: 'કાઢી નાખો',
relationship_actions: { relationship_actions: {
@@ -196,27 +198,19 @@ export const gu: LanguageTranslation = {
delete_relationship: 'કાઢી નાખો', delete_relationship: 'કાઢી નાખો',
}, },
}, },
empty_state: {
title: 'કોઈ સંબંધો નથી',
description: 'ટેબલ્સ કનેક્ટ કરવા માટે એક સંબંધ બનાવો',
},
},
dependencies_section: {
dependencies: 'નિર્ભરતાઓ',
filter: 'ફિલ્ટર',
collapse: 'સિકોડો',
dependency: { dependency: {
dependency: 'નિર્ભરતા',
table: 'ટેબલ', table: 'ટેબલ',
dependent_table: 'આધાર રાખેલું ટેબલ', dependent_table: 'નિર્ભરશીલ વ્યૂ',
delete_dependency: 'નિર્ભરતા કાઢી નાખો', delete_dependency: 'કાઢી નાખો',
dependency_actions: { dependency_actions: {
title: 'ક્રિયાઓ', title: 'ક્રિયાઓ',
delete_dependency: 'નિર્ભરતા કાઢી નાખો', delete_dependency: 'કાઢી નાખો',
}, },
}, },
empty_state: { empty_state: {
title: 'કોઈ નિર્ભરતાઓ નથી', title: 'કોઈ સંબંધો નથી',
description: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.', description: 'શરૂ કરવા માટે એક સંબંધ બનાવો',
}, },
}, },
@@ -240,6 +234,35 @@ export const gu: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuals',
tabs: {
areas: 'Areas',
notes: 'નોંધો',
},
},
notes_section: {
filter: 'ફિલ્ટર',
add_note: 'નોંધ ઉમેરો',
no_results: 'કોઈ નોંધો મળી નથી',
clear: 'ફિલ્ટર સાફ કરો',
empty_state: {
title: 'કોઈ નોંધો નથી',
description:
'કેનવાસ પર ટેક્સ્ટ એનોટેશન ઉમેરવા માટે નોંધ બનાવો',
},
note: {
empty_note: 'ખાલી નોંધ',
note_actions: {
title: 'નોંધ ક્રિયાઓ',
edit_content: 'સામગ્રી સંપાદિત કરો',
delete_note: 'નોંધ કાઢી નાખો',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -256,6 +279,7 @@ export const gu: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'કોઈ enum મૂલ્યો વ્યાખ્યાયિત નથી',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -278,7 +302,7 @@ export const gu: LanguageTranslation = {
show_all: 'બધું બતાવો', show_all: 'બધું બતાવો',
undo: 'અનડુ', undo: 'અનડુ',
redo: 'રીડુ', redo: 'રીડુ',
reorder_diagram: 'ડાયાગ્રામ ફરીથી વ્યવસ્થિત કરો', reorder_diagram: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -315,13 +339,13 @@ export const gu: LanguageTranslation = {
cancel: 'રદ કરો', cancel: 'રદ કરો',
back: 'પાછા', back: 'પાછા',
import_from_file: 'ફાઇલમાંથી આયાત કરો', import_from_file: 'ફાઇલમાંથી આયાત કરો',
empty_diagram: 'ખાલી ડાયાગ્રામ', empty_diagram: 'ખાલી ડેટાબેસ',
continue: 'ચાલુ રાખો', continue: 'ચાલુ રાખો',
import: 'આયાત કરો', import: 'આયાત કરો',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'ડાયાગ્રામ ખોલો', title: 'ડેટાબેસ ખોલો',
description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.', description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.',
table_columns: { table_columns: {
name: 'નામ', name: 'નામ',
@@ -331,6 +355,12 @@ export const gu: LanguageTranslation = {
}, },
cancel: 'રદ કરો', cancel: 'રદ કરો',
open: 'ખોલો', open: 'ખોલો',
diagram_actions: {
open: 'ખોલો',
duplicate: 'ડુપ્લિકેટ',
delete: 'કાઢી નાખો',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -480,9 +510,11 @@ export const gu: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'નવું ટેબલ', new_table: 'નવું ટેબલ',
new_view: 'નવું વ્યૂ',
new_relationship: 'નવો સંબંધ', new_relationship: 'નવો સંબંધ',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'નવી નોંધ',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -501,6 +533,9 @@ export const gu: LanguageTranslation = {
language_select: { language_select: {
change_language: 'ભાષા બદલો', change_language: 'ભાષા બદલો',
}, },
on: 'ચાલુ',
off: 'બંધ',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const hi: LanguageTranslation = { export const hi: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'नया',
browse: 'ब्राउज़',
tables: 'टेबल',
refs: 'रेफ्स',
dependencies: 'निर्भरताएं',
custom_types: 'कस्टम टाइप',
visuals: 'Visuals',
},
menu: { menu: {
file: { actions: {
file: 'फ़ाइल', actions: 'कार्य',
new: 'नया', new: 'नया...',
open: 'खोलें', browse: 'ब्राउज़ करें...',
save: 'सहेजें', save: 'सहेजें',
import: 'डेटाबेस आयात करें', import: 'डेटाबेस आयात करें',
export_sql: 'SQL निर्यात करें', export_sql: 'SQL निर्यात करें',
export_as: 'के रूप में निर्यात करें', export_as: 'के रूप में निर्यात करें',
delete_diagram: 'आरेख हटाएँ', delete_diagram: 'हटाएँ',
exit: 'बाहर जाएँ',
}, },
edit: { edit: {
edit: 'संपादित करें', edit: 'संपादित करें',
@@ -29,6 +37,7 @@ export const hi: LanguageTranslation = {
hide_field_attributes: 'फ़ील्ड विशेषताएँ छिपाएँ', hide_field_attributes: 'फ़ील्ड विशेषताएँ छिपाएँ',
show_field_attributes: 'फ़ील्ड विशेषताएँ दिखाएँ', show_field_attributes: 'फ़ील्ड विशेषताएँ दिखाएँ',
zoom_on_scroll: 'स्क्रॉल पर ज़ूम', zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
show_views: 'डेटाबेस व्यू',
theme: 'थीम', theme: 'थीम',
show_dependencies: 'निर्भरता दिखाएँ', show_dependencies: 'निर्भरता दिखाएँ',
hide_dependencies: 'निर्भरता छिपाएँ', hide_dependencies: 'निर्भरता छिपाएँ',
@@ -65,22 +74,13 @@ export const hi: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'आरेख पुनः व्यवस्थित करें', title: 'आरेख स्वचालित व्यवस्थित करें',
description: description:
'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?', 'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?',
reorder: 'पुनः व्यवस्थित करें', reorder: 'स्वचालित व्यवस्थित करें',
cancel: 'रद्द करें', cancel: 'रद्द करें',
}, },
multiple_schemas_alert: {
title: 'एकाधिक स्कीमा',
description:
'{{schemasCount}} स्कीमा इस आरेख में हैं। वर्तमान में प्रदर्शित: {{formattedSchemas}}।',
// TODO: Translate
show_me: 'Show me',
none: 'कोई नहीं',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'कॉपी असफल', title: 'कॉपी असफल',
@@ -116,14 +116,11 @@ export const hi: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'स्कीमा:',
filter_by_schema: 'स्कीमा द्वारा फ़िल्टर करें',
search_schema: 'स्कीमा खोजें...',
no_schemas_found: 'कोई स्कीमा नहीं मिला।',
view_all_options: 'सभी विकल्प देखें...', view_all_options: 'सभी विकल्प देखें...',
tables_section: { tables_section: {
tables: 'तालिकाएँ', tables: 'तालिकाएँ',
add_table: 'तालिका जोड़ें', add_table: 'तालिका जोड़ें',
add_view: 'व्यू जोड़ें',
filter: 'फ़िल्टर', filter: 'फ़िल्टर',
collapse: 'सभी को संक्षिप्त करें', collapse: 'सभी को संक्षिप्त करें',
// TODO: Translate // TODO: Translate
@@ -149,6 +146,7 @@ export const hi: LanguageTranslation = {
field_actions: { field_actions: {
title: 'फ़ील्ड विशेषताएँ', title: 'फ़ील्ड विशेषताएँ',
unique: 'अद्वितीय', unique: 'अद्वितीय',
auto_increment: 'ऑटो इंक्रीमेंट',
comments: 'टिप्पणियाँ', comments: 'टिप्पणियाँ',
no_comments: 'कोई टिप्पणी नहीं', no_comments: 'कोई टिप्पणी नहीं',
delete_field: 'फ़ील्ड हटाएँ', delete_field: 'फ़ील्ड हटाएँ',
@@ -164,6 +162,7 @@ export const hi: LanguageTranslation = {
title: 'सूचकांक विशेषताएँ', title: 'सूचकांक विशेषताएँ',
name: 'नाम', name: 'नाम',
unique: 'अद्वितीय', unique: 'अद्वितीय',
index_type: 'इंडेक्स प्रकार',
delete_index: 'सूचकांक हटाएँ', delete_index: 'सूचकांक हटाएँ',
}, },
table_actions: { table_actions: {
@@ -180,12 +179,15 @@ export const hi: LanguageTranslation = {
description: 'शुरू करने के लिए एक तालिका बनाएँ', description: 'शुरू करने के लिए एक तालिका बनाएँ',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'संबंध', refs: 'रेफ्स',
filter: 'फ़िल्टर', filter: 'फ़िल्टर',
add_relationship: 'संबंध जोड़ें',
collapse: 'सभी को संक्षिप्त करें', collapse: 'सभी को संक्षिप्त करें',
add_relationship: 'संबंध जोड़ें',
relationships: 'संबंध',
dependencies: 'निर्भरताएँ',
relationship: { relationship: {
relationship: 'संबंध',
primary: 'प्राथमिक तालिका', primary: 'प्राथमिक तालिका',
foreign: 'संदर्भित तालिका', foreign: 'संदर्भित तालिका',
cardinality: 'कार्डिनैलिटी', cardinality: 'कार्डिनैलिटी',
@@ -195,28 +197,19 @@ export const hi: LanguageTranslation = {
delete_relationship: 'हटाएँ', delete_relationship: 'हटाएँ',
}, },
}, },
empty_state: {
title: 'कोई संबंध नहीं',
description:
'तालिकाओं को कनेक्ट करने के लिए एक संबंध बनाएँ',
},
},
dependencies_section: {
dependencies: 'निर्भरताएँ',
filter: 'फ़िल्टर',
collapse: 'सिकोड़ें',
dependency: { dependency: {
dependency: 'निर्भरता',
table: 'तालिका', table: 'तालिका',
dependent_table: 'आश्रित तालिका', dependent_table: 'आश्रित दृश्य',
delete_dependency: 'निर्भरता हटाएँ', delete_dependency: 'हटाएँ',
dependency_actions: { dependency_actions: {
title: 'कार्रवाइयाँ', title: 'क्रियाँ',
delete_dependency: 'निर्भरता हटाएँ', delete_dependency: 'हटाएँ',
}, },
}, },
empty_state: { empty_state: {
title: 'कोई निर्भरता नहीं', title: 'कोई संबंध नहीं',
description: 'इस अनुभाग में कोई निर्भरता उपलब्ध नहीं है।', description: 'शुरू करने के लिए एक संबंध बनाएँ',
}, },
}, },
@@ -240,6 +233,35 @@ export const hi: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuals',
tabs: {
areas: 'Areas',
notes: 'नोट्स',
},
},
notes_section: {
filter: 'फ़िल्टर',
add_note: 'नोट जोड़ें',
no_results: 'कोई नोट नहीं मिला',
clear: 'फ़िल्टर साफ़ करें',
empty_state: {
title: 'कोई नोट नहीं',
description:
'कैनवास पर टेक्स्ट एनोटेशन जोड़ने के लिए एक नोट बनाएं',
},
note: {
empty_note: 'खाली नोट',
note_actions: {
title: 'नोट क्रियाएं',
edit_content: 'सामग्री संपादित करें',
delete_note: 'नोट हटाएं',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -256,6 +278,7 @@ export const hi: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'कोई enum मान परिभाषित नहीं',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -278,7 +301,7 @@ export const hi: LanguageTranslation = {
show_all: 'सभी दिखाएँ', show_all: 'सभी दिखाएँ',
undo: 'पूर्ववत करें', undo: 'पूर्ववत करें',
redo: 'पुनः करें', redo: 'पुनः करें',
reorder_diagram: 'आरेख पुनः व्यवस्थित करें', reorder_diagram: 'आरेख स्वचालित व्यवस्थित करें',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -318,13 +341,13 @@ export const hi: LanguageTranslation = {
back: 'वापस', back: 'वापस',
// TODO: Translate // TODO: Translate
import_from_file: 'Import from File', import_from_file: 'Import from File',
empty_diagram: 'खाली आरेख', empty_diagram: 'खाली डेटाबेस',
continue: 'जारी रखें', continue: 'जारी रखें',
import: 'आयात करें', import: 'आयात करें',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'आरेख खोलें', title: 'डेटाबेस खोलें',
description: 'नीचे दी गई सूची से एक आरेख चुनें।', description: 'नीचे दी गई सूची से एक आरेख चुनें।',
table_columns: { table_columns: {
name: 'नाम', name: 'नाम',
@@ -334,6 +357,12 @@ export const hi: LanguageTranslation = {
}, },
cancel: 'रद्द करें', cancel: 'रद्द करें',
open: 'खोलें', open: 'खोलें',
diagram_actions: {
open: 'खोलें',
duplicate: 'डुप्लिकेट',
delete: 'हटाएं',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -483,9 +512,11 @@ export const hi: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'नई तालिका', new_table: 'नई तालिका',
new_view: 'नया व्यू',
new_relationship: 'नया संबंध', new_relationship: 'नया संबंध',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'नया नोट',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -505,6 +536,9 @@ export const hi: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा बदलें', change_language: 'भाषा बदलें',
}, },
on: 'चालू',
off: 'बंद',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const hr: LanguageTranslation = { export const hr: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Novi',
browse: 'Pregledaj',
tables: 'Tablice',
refs: 'Refs',
dependencies: 'Ovisnosti',
custom_types: 'Prilagođeni Tipovi',
visuals: 'Vizuali',
},
menu: { menu: {
file: { actions: {
file: 'Datoteka', actions: 'Akcije',
new: 'Nova', new: 'Novi...',
open: 'Otvori', browse: 'Pregledaj...',
save: 'Spremi', save: 'Spremi',
import: 'Uvezi', import: 'Uvezi',
export_sql: 'Izvezi SQL', export_sql: 'Izvezi SQL',
export_as: 'Izvezi kao', export_as: 'Izvezi kao',
delete_diagram: 'Izbriši dijagram', delete_diagram: 'Izbriši',
exit: 'Izađi',
}, },
edit: { edit: {
edit: 'Uredi', edit: 'Uredi',
@@ -29,6 +37,7 @@ export const hr: LanguageTranslation = {
hide_field_attributes: 'Sakrij atribute polja', hide_field_attributes: 'Sakrij atribute polja',
show_field_attributes: 'Prikaži atribute polja', show_field_attributes: 'Prikaži atribute polja',
zoom_on_scroll: 'Zumiranje pri skrolanju', zoom_on_scroll: 'Zumiranje pri skrolanju',
show_views: 'Pogledi Baze Podataka',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Prikaži ovisnosti', show_dependencies: 'Prikaži ovisnosti',
hide_dependencies: 'Sakrij ovisnosti', hide_dependencies: 'Sakrij ovisnosti',
@@ -64,21 +73,13 @@ export const hr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Preuredi dijagram', title: 'Automatski preuredi dijagram',
description: description:
'Ova radnja će preurediti sve tablice u dijagramu. Želite li nastaviti?', 'Ova radnja će preurediti sve tablice u dijagramu. Želite li nastaviti?',
reorder: 'Preuredi', reorder: 'Automatski preuredi',
cancel: 'Odustani', cancel: 'Odustani',
}, },
multiple_schemas_alert: {
title: 'Više shema',
description:
'{{schemasCount}} shema u ovom dijagramu. Trenutno prikazano: {{formattedSchemas}}.',
show_me: 'Prikaži mi',
none: 'nijedna',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Kopiranje neuspješno', title: 'Kopiranje neuspješno',
@@ -113,14 +114,11 @@ export const hr: LanguageTranslation = {
copied: 'Kopirano!', copied: 'Kopirano!',
side_panel: { side_panel: {
schema: 'Shema:',
filter_by_schema: 'Filtriraj po shemi',
search_schema: 'Pretraži shemu...',
no_schemas_found: 'Nema pronađenih shema.',
view_all_options: 'Prikaži sve opcije...', view_all_options: 'Prikaži sve opcije...',
tables_section: { tables_section: {
tables: 'Tablice', tables: 'Tablice',
add_table: 'Dodaj tablicu', add_table: 'Dodaj tablicu',
add_view: 'Dodaj Pogled',
filter: 'Filtriraj', filter: 'Filtriraj',
collapse: 'Sažmi sve', collapse: 'Sažmi sve',
clear: 'Očisti filter', clear: 'Očisti filter',
@@ -145,6 +143,7 @@ export const hr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Atributi polja', title: 'Atributi polja',
unique: 'Jedinstven', unique: 'Jedinstven',
auto_increment: 'Automatsko povećavanje',
character_length: 'Maksimalna dužina', character_length: 'Maksimalna dužina',
precision: 'Preciznost', precision: 'Preciznost',
scale: 'Skala', scale: 'Skala',
@@ -158,6 +157,7 @@ export const hr: LanguageTranslation = {
title: 'Atributi indeksa', title: 'Atributi indeksa',
name: 'Naziv', name: 'Naziv',
unique: 'Jedinstven', unique: 'Jedinstven',
index_type: 'Vrsta indeksa',
delete_index: 'Izbriši indeks', delete_index: 'Izbriši indeks',
}, },
table_actions: { table_actions: {
@@ -174,12 +174,15 @@ export const hr: LanguageTranslation = {
description: 'Stvorite tablicu za početak', description: 'Stvorite tablicu za početak',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Veze', refs: 'Refs',
filter: 'Filtriraj', filter: 'Filtriraj',
add_relationship: 'Dodaj vezu',
collapse: 'Sažmi sve', collapse: 'Sažmi sve',
add_relationship: 'Dodaj vezu',
relationships: 'Veze',
dependencies: 'Ovisnosti',
relationship: { relationship: {
relationship: 'Veza',
primary: 'Primarna tablica', primary: 'Primarna tablica',
foreign: 'Referentna tablica', foreign: 'Referentna tablica',
cardinality: 'Kardinalnost', cardinality: 'Kardinalnost',
@@ -189,16 +192,8 @@ export const hr: LanguageTranslation = {
delete_relationship: 'Izbriši', delete_relationship: 'Izbriši',
}, },
}, },
empty_state: {
title: 'Nema veza',
description: 'Stvorite vezu za povezivanje tablica',
},
},
dependencies_section: {
dependencies: 'Ovisnosti',
filter: 'Filtriraj',
collapse: 'Sažmi sve',
dependency: { dependency: {
dependency: 'Ovisnost',
table: 'Tablica', table: 'Tablica',
dependent_table: 'Ovisni pogled', dependent_table: 'Ovisni pogled',
delete_dependency: 'Izbriši', delete_dependency: 'Izbriši',
@@ -208,8 +203,8 @@ export const hr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Nema ovisnosti', title: 'Nema veze',
description: 'Stvorite pogled za početak', description: 'Stvorite vezu za početak',
}, },
}, },
@@ -234,6 +229,34 @@ export const hr: LanguageTranslation = {
}, },
}, },
visuals_section: {
visuals: 'Vizuali',
tabs: {
areas: 'Područja',
notes: 'Bilješke',
},
},
notes_section: {
filter: 'Filtriraj',
add_note: 'Dodaj Bilješku',
no_results: 'Nije pronađena nijedna bilješka',
clear: 'Očisti Filter',
empty_state: {
title: 'Nema Bilješki',
description:
'Kreirajte bilješku za dodavanje tekstualnih napomena na platnu',
},
note: {
empty_note: 'Prazna bilješka',
note_actions: {
title: 'Akcije Bilješke',
edit_content: 'Uredi Sadržaj',
delete_note: 'Obriši Bilješku',
},
},
},
custom_types_section: { custom_types_section: {
custom_types: 'Prilagođeni tipovi', custom_types: 'Prilagođeni tipovi',
filter: 'Filtriraj', filter: 'Filtriraj',
@@ -250,6 +273,7 @@ export const hr: LanguageTranslation = {
enum_values: 'Enum vrijednosti', enum_values: 'Enum vrijednosti',
composite_fields: 'Polja', composite_fields: 'Polja',
no_fields: 'Nema definiranih polja', no_fields: 'Nema definiranih polja',
no_values: 'Nema definiranih enum vrijednosti',
field_name_placeholder: 'Naziv polja', field_name_placeholder: 'Naziv polja',
field_type_placeholder: 'Odaberi tip', field_type_placeholder: 'Odaberi tip',
add_field: 'Dodaj polje', add_field: 'Dodaj polje',
@@ -273,7 +297,7 @@ export const hr: LanguageTranslation = {
show_all: 'Prikaži sve', show_all: 'Prikaži sve',
undo: 'Poništi', undo: 'Poništi',
redo: 'Ponovi', redo: 'Ponovi',
reorder_diagram: 'Preuredi dijagram', reorder_diagram: 'Automatski preuredi dijagram',
highlight_overlapping_tables: 'Istakni preklapajuće tablice', highlight_overlapping_tables: 'Istakni preklapajuće tablice',
clear_custom_type_highlight: 'Ukloni isticanje za "{{typeName}}"', clear_custom_type_highlight: 'Ukloni isticanje za "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -309,13 +333,13 @@ export const hr: LanguageTranslation = {
cancel: 'Odustani', cancel: 'Odustani',
import_from_file: 'Uvezi iz datoteke', import_from_file: 'Uvezi iz datoteke',
back: 'Natrag', back: 'Natrag',
empty_diagram: 'Prazan dijagram', empty_diagram: 'Prazna baza podataka',
continue: 'Nastavi', continue: 'Nastavi',
import: 'Uvezi', import: 'Uvezi',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Otvori dijagram', title: 'Otvori bazu podataka',
description: 'Odaberite dijagram za otvaranje iz popisa ispod.', description: 'Odaberite dijagram za otvaranje iz popisa ispod.',
table_columns: { table_columns: {
name: 'Naziv', name: 'Naziv',
@@ -325,6 +349,12 @@ export const hr: LanguageTranslation = {
}, },
cancel: 'Odustani', cancel: 'Odustani',
open: 'Otvori', open: 'Otvori',
diagram_actions: {
open: 'Otvori',
duplicate: 'Dupliciraj',
delete: 'Obriši',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -473,8 +503,10 @@ export const hr: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Nova tablica', new_table: 'Nova tablica',
new_view: 'Novi Pogled',
new_relationship: 'Nova veza', new_relationship: 'Nova veza',
new_area: 'Novo područje', new_area: 'Novo područje',
new_note: 'Nova Bilješka',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -493,6 +525,9 @@ export const hr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Jezik', change_language: 'Jezik',
}, },
on: 'Uključeno',
off: 'Isključeno',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const id_ID: LanguageTranslation = { export const id_ID: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Baru',
browse: 'Jelajahi',
tables: 'Tabel',
refs: 'Refs',
dependencies: 'Ketergantungan',
custom_types: 'Tipe Kustom',
visuals: 'Visual',
},
menu: { menu: {
file: { actions: {
file: 'Berkas', actions: 'Aksi',
new: 'Buat Baru', new: 'Baru...',
open: 'Buka', browse: 'Jelajahi...',
save: 'Simpan', save: 'Simpan',
import: 'Impor Database', import: 'Impor Database',
export_sql: 'Ekspor SQL', export_sql: 'Ekspor SQL',
export_as: 'Ekspor Sebagai', export_as: 'Ekspor Sebagai',
delete_diagram: 'Hapus Diagram', delete_diagram: 'Hapus',
exit: 'Keluar',
}, },
edit: { edit: {
edit: 'Ubah', edit: 'Ubah',
@@ -29,6 +37,7 @@ export const id_ID: LanguageTranslation = {
hide_field_attributes: 'Sembunyikan Atribut Kolom', hide_field_attributes: 'Sembunyikan Atribut Kolom',
show_field_attributes: 'Tampilkan Atribut Kolom', show_field_attributes: 'Tampilkan Atribut Kolom',
zoom_on_scroll: 'Perbesar saat Scroll', zoom_on_scroll: 'Perbesar saat Scroll',
show_views: 'Tampilan Database',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Tampilkan Dependensi', show_dependencies: 'Tampilkan Dependensi',
hide_dependencies: 'Sembunyikan Dependensi', hide_dependencies: 'Sembunyikan Dependensi',
@@ -65,22 +74,13 @@ export const id_ID: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Atur Ulang Diagram', title: 'Atur Otomatis Diagram',
description: description:
'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?', 'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?',
reorder: 'Atur Ulang', reorder: 'Atur Otomatis',
cancel: 'Batal', cancel: 'Batal',
}, },
multiple_schemas_alert: {
title: 'Schema Lebih dari satu',
description:
'{{schemasCount}} schema di diagram ini. Sedang ditampilkan: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'Tidak ada',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Gagal menyalin', title: 'Gagal menyalin',
@@ -115,14 +115,11 @@ export const id_ID: LanguageTranslation = {
copied: 'Tersalin!', copied: 'Tersalin!',
side_panel: { side_panel: {
schema: 'Skema:',
filter_by_schema: 'Saring berdasarkan skema',
search_schema: 'Cari skema...',
no_schemas_found: 'Tidak ada skema yang ditemukan.',
view_all_options: 'Tampilkan Semua Pilihan...', view_all_options: 'Tampilkan Semua Pilihan...',
tables_section: { tables_section: {
tables: 'Tabel', tables: 'Tabel',
add_table: 'Tambah Tabel', add_table: 'Tambah Tabel',
add_view: 'Tambah Tampilan',
filter: 'Saring', filter: 'Saring',
collapse: 'Lipat Semua', collapse: 'Lipat Semua',
// TODO: Translate // TODO: Translate
@@ -148,6 +145,7 @@ export const id_ID: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Atribut Kolom', title: 'Atribut Kolom',
unique: 'Unik', unique: 'Unik',
auto_increment: 'Kenaikan Otomatis',
comments: 'Komentar', comments: 'Komentar',
no_comments: 'Tidak ada komentar', no_comments: 'Tidak ada komentar',
delete_field: 'Hapus Kolom', delete_field: 'Hapus Kolom',
@@ -163,6 +161,7 @@ export const id_ID: LanguageTranslation = {
title: 'Atribut Indeks', title: 'Atribut Indeks',
name: 'Nama', name: 'Nama',
unique: 'Unik', unique: 'Unik',
index_type: 'Tipe Indeks',
delete_index: 'Hapus Indeks', delete_index: 'Hapus Indeks',
}, },
table_actions: { table_actions: {
@@ -179,12 +178,15 @@ export const id_ID: LanguageTranslation = {
description: 'Buat tabel untuk memulai', description: 'Buat tabel untuk memulai',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Hubungan', refs: 'Refs',
filter: 'Saring', filter: 'Saring',
add_relationship: 'Tambah Hubungan',
collapse: 'Lipat Semua', collapse: 'Lipat Semua',
add_relationship: 'Tambah Hubungan',
relationships: 'Hubungan',
dependencies: 'Dependensi',
relationship: { relationship: {
relationship: 'Hubungan',
primary: 'Tabel Primer', primary: 'Tabel Primer',
foreign: 'Tabel Referensi', foreign: 'Tabel Referensi',
cardinality: 'Kardinalitas', cardinality: 'Kardinalitas',
@@ -194,16 +196,8 @@ export const id_ID: LanguageTranslation = {
delete_relationship: 'Hapus', delete_relationship: 'Hapus',
}, },
}, },
empty_state: {
title: 'Tidak ada hubungan',
description: 'Buat hubungan untuk menghubungkan tabel',
},
},
dependencies_section: {
dependencies: 'Dependensi',
filter: 'Saring',
collapse: 'Lipat Semua',
dependency: { dependency: {
dependency: 'Dependensi',
table: 'Tabel', table: 'Tabel',
dependent_table: 'Tampilan Dependen', dependent_table: 'Tampilan Dependen',
delete_dependency: 'Hapus', delete_dependency: 'Hapus',
@@ -213,8 +207,8 @@ export const id_ID: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Tidak ada dependensi', title: 'Tidak ada hubungan',
description: 'Buat tampilan untuk memulai', description: 'Buat hubungan untuk memulai',
}, },
}, },
@@ -238,6 +232,35 @@ export const id_ID: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visual',
tabs: {
areas: 'Areas',
notes: 'Catatan',
},
},
notes_section: {
filter: 'Filter',
add_note: 'Tambah Catatan',
no_results: 'Tidak ada catatan ditemukan',
clear: 'Hapus Filter',
empty_state: {
title: 'Tidak Ada Catatan',
description:
'Buat catatan untuk menambahkan anotasi teks di kanvas',
},
note: {
empty_note: 'Catatan kosong',
note_actions: {
title: 'Aksi Catatan',
edit_content: 'Edit Konten',
delete_note: 'Hapus Catatan',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -254,6 +277,7 @@ export const id_ID: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Tidak ada nilai enum yang ditentukan',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -276,7 +300,7 @@ export const id_ID: LanguageTranslation = {
show_all: 'Tampilkan Semua', show_all: 'Tampilkan Semua',
undo: 'Undo', undo: 'Undo',
redo: 'Redo', redo: 'Redo',
reorder_diagram: 'Atur Ulang Diagram', reorder_diagram: 'Atur Otomatis Diagram',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -314,13 +338,13 @@ export const id_ID: LanguageTranslation = {
cancel: 'Batal', cancel: 'Batal',
import_from_file: 'Impor dari file', import_from_file: 'Impor dari file',
back: 'Kembali', back: 'Kembali',
empty_diagram: 'Diagram Kosong', empty_diagram: 'Database Kosong',
continue: 'Lanjutkan', continue: 'Lanjutkan',
import: 'Impor', import: 'Impor',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Buka Diagram', title: 'Buka Database',
description: 'Pilih diagram untuk dibuka dari daftar di bawah.', description: 'Pilih diagram untuk dibuka dari daftar di bawah.',
table_columns: { table_columns: {
name: 'Name', name: 'Name',
@@ -330,6 +354,12 @@ export const id_ID: LanguageTranslation = {
}, },
cancel: 'Batal', cancel: 'Batal',
open: 'Buka', open: 'Buka',
diagram_actions: {
open: 'Buka',
duplicate: 'Duplikat',
delete: 'Hapus',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -479,9 +509,11 @@ export const id_ID: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Tabel Baru', new_table: 'Tabel Baru',
new_view: 'Tampilan Baru',
new_relationship: 'Hubungan Baru', new_relationship: 'Hubungan Baru',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Catatan Baru',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -500,6 +532,9 @@ export const id_ID: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Bahasa', change_language: 'Bahasa',
}, },
on: 'Aktif',
off: 'Nonaktif',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ja: LanguageTranslation = { export const ja: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '新規',
browse: '参照',
tables: 'テーブル',
refs: '参照',
dependencies: '依存関係',
custom_types: 'カスタムタイプ',
visuals: 'ビジュアル',
},
menu: { menu: {
file: { actions: {
file: 'ファイル', actions: 'アクション',
new: '新規', new: '新規...',
open: '開く', browse: '参照...',
save: '保存', save: '保存',
import: 'データベースをインポート', import: 'データベースをインポート',
export_sql: 'SQLをエクスポート', export_sql: 'SQLをエクスポート',
export_as: '形式を指定してエクスポート', export_as: '形式を指定してエクスポート',
delete_diagram: 'ダイアグラムを削除', delete_diagram: '削除',
exit: '終了',
}, },
edit: { edit: {
edit: '編集', edit: '編集',
@@ -29,6 +37,7 @@ export const ja: LanguageTranslation = {
hide_field_attributes: 'フィールド属性を非表示', hide_field_attributes: 'フィールド属性を非表示',
show_field_attributes: 'フィールド属性を表示', show_field_attributes: 'フィールド属性を表示',
zoom_on_scroll: 'スクロールでズーム', zoom_on_scroll: 'スクロールでズーム',
show_views: 'データベースビュー',
theme: 'テーマ', theme: 'テーマ',
// TODO: Translate // TODO: Translate
show_dependencies: 'Show Dependencies', show_dependencies: 'Show Dependencies',
@@ -67,22 +76,13 @@ export const ja: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'ダイアグラムを並べ替え', title: 'ダイアグラムを自動配置',
description: description:
'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?', 'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?',
reorder: '並べ替え', reorder: '自動配置',
cancel: 'キャンセル', cancel: 'キャンセル',
}, },
multiple_schemas_alert: {
title: '複数のスキーマ',
description:
'このダイアグラムには{{schemasCount}}個のスキーマがあります。現在表示中: {{formattedSchemas}}。',
// TODO: Translate
show_me: 'Show me',
none: 'なし',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'コピー失敗', title: 'コピー失敗',
@@ -119,14 +119,11 @@ export const ja: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'スキーマ:',
filter_by_schema: 'スキーマでフィルタ',
search_schema: 'スキーマを検索...',
no_schemas_found: 'スキーマが見つかりません。',
view_all_options: 'すべてのオプションを表示...', view_all_options: 'すべてのオプションを表示...',
tables_section: { tables_section: {
tables: 'テーブル', tables: 'テーブル',
add_table: 'テーブルを追加', add_table: 'テーブルを追加',
add_view: 'ビューを追加',
filter: 'フィルタ', filter: 'フィルタ',
collapse: 'すべて折りたたむ', collapse: 'すべて折りたたむ',
// TODO: Translate // TODO: Translate
@@ -152,6 +149,7 @@ export const ja: LanguageTranslation = {
field_actions: { field_actions: {
title: 'フィールド属性', title: 'フィールド属性',
unique: 'ユニーク', unique: 'ユニーク',
auto_increment: 'オートインクリメント',
comments: 'コメント', comments: 'コメント',
no_comments: 'コメントがありません', no_comments: 'コメントがありません',
delete_field: 'フィールドを削除', delete_field: 'フィールドを削除',
@@ -167,6 +165,7 @@ export const ja: LanguageTranslation = {
title: 'インデックス属性', title: 'インデックス属性',
name: '名前', name: '名前',
unique: 'ユニーク', unique: 'ユニーク',
index_type: 'インデックスタイプ',
delete_index: 'インデックスを削除', delete_index: 'インデックスを削除',
}, },
table_actions: { table_actions: {
@@ -183,12 +182,15 @@ export const ja: LanguageTranslation = {
description: 'テーブルを作成して開始してください', description: 'テーブルを作成して開始してください',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'リレーションシップ', refs: '参照',
filter: 'フィルタ', filter: 'フィルタ',
add_relationship: 'リレーションシップを追加',
collapse: 'すべて折りたたむ', collapse: 'すべて折りたたむ',
add_relationship: 'リレーションシップを追加',
relationships: 'リレーションシップ',
dependencies: '依存関係',
relationship: { relationship: {
relationship: 'リレーションシップ',
primary: '主テーブル', primary: '主テーブル',
foreign: '参照テーブル', foreign: '参照テーブル',
cardinality: 'カーディナリティ', cardinality: 'カーディナリティ',
@@ -198,29 +200,20 @@ export const ja: LanguageTranslation = {
delete_relationship: '削除', delete_relationship: '削除',
}, },
}, },
empty_state: {
title: 'リレーションシップがありません',
description:
'テーブルを接続するためにリレーションシップを作成してください',
},
},
// TODO: Translate
dependencies_section: {
dependencies: 'Dependencies',
filter: 'Filter',
collapse: 'Collapse All',
dependency: { dependency: {
table: 'Table', dependency: '依存関係',
dependent_table: 'Dependent View', table: 'テーブル',
delete_dependency: 'Delete', dependent_table: '依存ビュー',
delete_dependency: '削除',
dependency_actions: { dependency_actions: {
title: 'Actions', title: '操作',
delete_dependency: 'Delete', delete_dependency: '削除',
}, },
}, },
empty_state: { empty_state: {
title: 'No dependencies', title: 'リレーションシップがありません',
description: 'Create a view to get started', description:
'開始するためにリレーションシップを作成してください',
}, },
}, },
@@ -244,6 +237,35 @@ export const ja: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'ビジュアル',
tabs: {
areas: 'Areas',
notes: 'ノート',
},
},
notes_section: {
filter: 'フィルター',
add_note: 'ノートを追加',
no_results: 'ノートが見つかりません',
clear: 'フィルターをクリア',
empty_state: {
title: 'ノートがありません',
description:
'キャンバス上にテキスト注釈を追加するためのノートを作成',
},
note: {
empty_note: '空のノート',
note_actions: {
title: 'ノートアクション',
edit_content: 'コンテンツを編集',
delete_note: 'ノートを削除',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -260,6 +282,7 @@ export const ja: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '列挙値が定義されていません',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -282,7 +305,7 @@ export const ja: LanguageTranslation = {
show_all: 'すべて表示', show_all: 'すべて表示',
undo: '元に戻す', undo: '元に戻す',
redo: 'やり直し', redo: 'やり直し',
reorder_diagram: 'ダイアグラムを並べ替え', reorder_diagram: 'ダイアグラムを自動配置',
// TODO: Translate // TODO: Translate
highlight_overlapping_tables: 'Highlight Overlapping Tables', highlight_overlapping_tables: 'Highlight Overlapping Tables',
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
@@ -320,13 +343,13 @@ export const ja: LanguageTranslation = {
back: '戻る', back: '戻る',
// TODO: Translate // TODO: Translate
import_from_file: 'Import from File', import_from_file: 'Import from File',
empty_diagram: '空のダイアグラム', empty_diagram: '空のデータベース',
continue: '続行', continue: '続行',
import: 'インポート', import: 'インポート',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'ダイアグラムを開く', title: 'データベースを開く',
description: '以下のリストからダイアグラムを選択してください。', description: '以下のリストからダイアグラムを選択してください。',
table_columns: { table_columns: {
name: '名前', name: '名前',
@@ -336,6 +359,12 @@ export const ja: LanguageTranslation = {
}, },
cancel: 'キャンセル', cancel: 'キャンセル',
open: '開く', open: '開く',
diagram_actions: {
open: '開く',
duplicate: '複製',
delete: '削除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -485,9 +514,11 @@ export const ja: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '新しいテーブル', new_table: '新しいテーブル',
new_view: '新しいビュー',
new_relationship: '新しいリレーションシップ', new_relationship: '新しいリレーションシップ',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: '新しいメモ',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -507,6 +538,9 @@ export const ja: LanguageTranslation = {
language_select: { language_select: {
change_language: '言語', change_language: '言語',
}, },
on: 'オン',
off: 'オフ',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ko_KR: LanguageTranslation = { export const ko_KR: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '새로 만들기',
browse: '찾아보기',
tables: '테이블',
refs: 'Refs',
dependencies: '종속성',
custom_types: '사용자 지정 타입',
visuals: '시각화',
},
menu: { menu: {
file: { actions: {
file: '파일', actions: '작업',
new: '새 다이어그램', new: '새로 만들기...',
open: '열기', browse: '찾아보기...',
save: '저장', save: '저장',
import: '데이터베이스 가져오기', import: '데이터베이스 가져오기',
export_sql: 'SQL로 저장', export_sql: 'SQL로 저장',
export_as: '다른 형식으로 저장', export_as: '다른 형식으로 저장',
delete_diagram: '다이어그램 삭제', delete_diagram: '삭제',
exit: '종료',
}, },
edit: { edit: {
edit: '편집', edit: '편집',
@@ -29,6 +37,7 @@ export const ko_KR: LanguageTranslation = {
hide_field_attributes: '필드 속성 숨기기', hide_field_attributes: '필드 속성 숨기기',
show_field_attributes: '필드 속성 보이기', show_field_attributes: '필드 속성 보이기',
zoom_on_scroll: '스크롤 시 확대', zoom_on_scroll: '스크롤 시 확대',
show_views: '데이터베이스 뷰',
theme: '테마', theme: '테마',
show_dependencies: '종속성 보이기', show_dependencies: '종속성 보이기',
hide_dependencies: '종속성 숨기기', hide_dependencies: '종속성 숨기기',
@@ -65,22 +74,13 @@ export const ko_KR: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: '다이어그램 정렬', title: '다이어그램 자동 정렬',
description: description:
'이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?', '이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?',
reorder: '정렬', reorder: '자동 정렬',
cancel: '취소', cancel: '취소',
}, },
multiple_schemas_alert: {
title: '다중 스키마',
description:
'현재 다이어그램에 {{schemasCount}}개의 스키마가 있습니다. Currently displaying: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: '없음',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: '복사 실패', title: '복사 실패',
@@ -115,14 +115,11 @@ export const ko_KR: LanguageTranslation = {
copied: '복사됨!', copied: '복사됨!',
side_panel: { side_panel: {
schema: '스키마:',
filter_by_schema: '스키마로 필터링',
search_schema: '스키마 검색...',
no_schemas_found: '스키마를 찾을 수 없습니다.',
view_all_options: '전체 옵션 보기...', view_all_options: '전체 옵션 보기...',
tables_section: { tables_section: {
tables: '테이블', tables: '테이블',
add_table: '테이블 추가', add_table: '테이블 추가',
add_view: '뷰 추가',
filter: '필터', filter: '필터',
collapse: '모두 접기', collapse: '모두 접기',
// TODO: Translate // TODO: Translate
@@ -148,6 +145,7 @@ export const ko_KR: LanguageTranslation = {
field_actions: { field_actions: {
title: '필드 속성', title: '필드 속성',
unique: '유니크 여부', unique: '유니크 여부',
auto_increment: '자동 증가',
comments: '주석', comments: '주석',
no_comments: '주석 없음', no_comments: '주석 없음',
delete_field: '필드 삭제', delete_field: '필드 삭제',
@@ -163,6 +161,7 @@ export const ko_KR: LanguageTranslation = {
title: '인덱스 속성', title: '인덱스 속성',
name: '인덱스 명', name: '인덱스 명',
unique: '유니크 여부', unique: '유니크 여부',
index_type: '인덱스 타입',
delete_index: '인덱스 삭제', delete_index: '인덱스 삭제',
}, },
table_actions: { table_actions: {
@@ -179,12 +178,15 @@ export const ko_KR: LanguageTranslation = {
description: '테이블을 만들어 시작하세요.', description: '테이블을 만들어 시작하세요.',
}, },
}, },
relationships_section: { refs_section: {
relationships: '연관 관계', refs: 'Refs',
filter: '필터', filter: '필터',
add_relationship: '연관 관계 추가',
collapse: '모두 접기', collapse: '모두 접기',
add_relationship: '연관 관계 추가',
relationships: '연관 관계',
dependencies: '종속성',
relationship: { relationship: {
relationship: '연관 관계',
primary: '주 테이블', primary: '주 테이블',
foreign: '참조 테이블', foreign: '참조 테이블',
cardinality: '카디널리티', cardinality: '카디널리티',
@@ -194,16 +196,8 @@ export const ko_KR: LanguageTranslation = {
delete_relationship: '연관 관계 삭제', delete_relationship: '연관 관계 삭제',
}, },
}, },
empty_state: {
title: '연관 관계',
description: '테이블 연결을 위해 연관 관계를 생성하세요',
},
},
dependencies_section: {
dependencies: '종속성',
filter: '필터',
collapse: '모두 접기',
dependency: { dependency: {
dependency: '종속성',
table: '테이블', table: '테이블',
dependent_table: '뷰 테이블', dependent_table: '뷰 테이블',
delete_dependency: '삭제', delete_dependency: '삭제',
@@ -213,8 +207,8 @@ export const ko_KR: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '뷰 테이블 없음', title: '연관 관계 없음',
description: '뷰 테이블을 만들어 시작하세요.', description: '연관 관계를 만들어 시작하세요.',
}, },
}, },
@@ -238,6 +232,35 @@ export const ko_KR: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: '시각화',
tabs: {
areas: 'Areas',
notes: '메모',
},
},
notes_section: {
filter: '필터',
add_note: '메모 추가',
no_results: '메모를 찾을 수 없습니다',
clear: '필터 지우기',
empty_state: {
title: '메모 없음',
description:
'캔버스에 텍스트 주석을 추가하려면 메모를 만드세요',
},
note: {
empty_note: '빈 메모',
note_actions: {
title: '메모 작업',
edit_content: '내용 편집',
delete_note: '메모 삭제',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -254,6 +277,7 @@ export const ko_KR: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '정의된 열거형 값이 없습니다',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -276,7 +300,7 @@ export const ko_KR: LanguageTranslation = {
show_all: '전체 저장', show_all: '전체 저장',
undo: '실행 취소', undo: '실행 취소',
redo: '다시 실행', redo: '다시 실행',
reorder_diagram: '다이어그램 정렬', reorder_diagram: '다이어그램 자동 정렬',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -314,13 +338,13 @@ export const ko_KR: LanguageTranslation = {
cancel: '취소', cancel: '취소',
back: '뒤로가기', back: '뒤로가기',
import_from_file: '파일에서 가져오기', import_from_file: '파일에서 가져오기',
empty_diagram: '빈 다이어그램으로 시작', empty_diagram: '빈 데이터베이스',
continue: '계속', continue: '계속',
import: '가져오기', import: '가져오기',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: '다이어그램 열기', title: '데이터베이스 열기',
description: '아래의 목록에서 다이어그램을 선택하세요.', description: '아래의 목록에서 다이어그램을 선택하세요.',
table_columns: { table_columns: {
name: '이름', name: '이름',
@@ -330,6 +354,12 @@ export const ko_KR: LanguageTranslation = {
}, },
cancel: '취소', cancel: '취소',
open: '열기', open: '열기',
diagram_actions: {
open: '열기',
duplicate: '복제',
delete: '삭제',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -476,9 +506,11 @@ export const ko_KR: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '새 테이블', new_table: '새 테이블',
new_view: '새 뷰',
new_relationship: '새 연관관계', new_relationship: '새 연관관계',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: '새 메모',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -497,6 +529,9 @@ export const ko_KR: LanguageTranslation = {
language_select: { language_select: {
change_language: '언어', change_language: '언어',
}, },
on: '켜기',
off: '끄기',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const mr: LanguageTranslation = { export const mr: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'नवीन',
browse: 'ब्राउज',
tables: 'टेबल',
refs: 'Refs',
dependencies: 'अवलंबने',
custom_types: 'कस्टम प्रकार',
visuals: 'Visuals',
},
menu: { menu: {
file: { actions: {
file: 'फाइल', actions: 'क्रिया',
new: 'नवीन', new: 'नवीन...',
open: 'उघडा', browse: 'ब्राउज करा...',
save: 'जतन करा', save: 'जतन करा',
import: 'डेटाबेस इम्पोर्ट करा', import: 'डेटाबेस इम्पोर्ट करा',
export_sql: 'SQL एक्स्पोर्ट करा', export_sql: 'SQL एक्स्पोर्ट करा',
export_as: 'म्हणून एक्स्पोर्ट करा', export_as: 'म्हणून एक्स्पोर्ट करा',
delete_diagram: 'आरेख हटवा', delete_diagram: 'हटवा',
exit: 'बाहेर पडा',
}, },
edit: { edit: {
edit: 'संपादन करा', edit: 'संपादन करा',
@@ -29,6 +37,7 @@ export const mr: LanguageTranslation = {
hide_field_attributes: 'फील्ड गुणधर्म लपवा', hide_field_attributes: 'फील्ड गुणधर्म लपवा',
show_field_attributes: 'फील्ड गुणधर्म दाखवा', show_field_attributes: 'फील्ड गुणधर्म दाखवा',
zoom_on_scroll: 'स्क्रोलवर झूम करा', zoom_on_scroll: 'स्क्रोलवर झूम करा',
show_views: 'डेटाबेस व्ह्यूज',
theme: 'थीम', theme: 'थीम',
show_dependencies: 'डिपेंडेन्सि दाखवा', show_dependencies: 'डिपेंडेन्सि दाखवा',
hide_dependencies: 'डिपेंडेन्सि लपवा', hide_dependencies: 'डिपेंडेन्सि लपवा',
@@ -66,22 +75,13 @@ export const mr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'आरेख पुनःक्रमित करा', title: 'आरेख स्वयंचलित व्यवस्थित करा',
description: description:
'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?', 'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?',
reorder: 'पुनःक्रमित करा', reorder: 'स्वयंचलित व्यवस्थित करा',
cancel: 'रद्द करा', cancel: 'रद्द करा',
}, },
multiple_schemas_alert: {
title: 'एकाधिक स्कीमा',
description:
'{{schemasCount}} स्कीमा या आरेखात आहेत. सध्या दाखवत आहोत: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'काहीही नाही',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'कॉपी अयशस्वी', title: 'कॉपी अयशस्वी',
@@ -118,14 +118,11 @@ export const mr: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'स्कीमा:',
filter_by_schema: 'स्कीमा द्वारे फिल्टर करा',
search_schema: 'स्कीमा शोधा...',
no_schemas_found: 'कोणतेही स्कीमा सापडले नाहीत.',
view_all_options: 'सर्व पर्याय पहा...', view_all_options: 'सर्व पर्याय पहा...',
tables_section: { tables_section: {
tables: 'टेबल्स', tables: 'टेबल्स',
add_table: 'टेबल जोडा', add_table: 'टेबल जोडा',
add_view: 'व्ह्यू जोडा',
filter: 'फिल्टर', filter: 'फिल्टर',
collapse: 'सर्व संकुचित करा', collapse: 'सर्व संकुचित करा',
// TODO: Translate // TODO: Translate
@@ -151,6 +148,7 @@ export const mr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'फील्ड गुणधर्म', title: 'फील्ड गुणधर्म',
unique: 'युनिक', unique: 'युनिक',
auto_increment: 'ऑटो इंक्रिमेंट',
comments: 'टिप्पण्या', comments: 'टिप्पण्या',
no_comments: 'कोणत्याही टिप्पणी नाहीत', no_comments: 'कोणत्याही टिप्पणी नाहीत',
delete_field: 'फील्ड हटवा', delete_field: 'फील्ड हटवा',
@@ -166,6 +164,7 @@ export const mr: LanguageTranslation = {
title: 'इंडेक्स गुणधर्म', title: 'इंडेक्स गुणधर्म',
name: 'नाव', name: 'नाव',
unique: 'युनिक', unique: 'युनिक',
index_type: 'इंडेक्स प्रकार',
delete_index: 'इंडेक्स हटवा', delete_index: 'इंडेक्स हटवा',
}, },
table_actions: { table_actions: {
@@ -183,12 +182,15 @@ export const mr: LanguageTranslation = {
description: 'सुरू करण्यासाठी एक टेबल तयार करा', description: 'सुरू करण्यासाठी एक टेबल तयार करा',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'रिलेशनशिप', refs: 'Refs',
filter: 'फिल्टर', filter: 'फिल्टर',
add_relationship: 'रिलेशनशिप जोडा',
collapse: 'सर्व संकुचित करा', collapse: 'सर्व संकुचित करा',
add_relationship: 'रिलेशनशिप जोडा',
relationships: 'रिलेशनशिप',
dependencies: 'डिपेंडेन्सि',
relationship: { relationship: {
relationship: 'रिलेशनशिप',
primary: 'प्राथमिक टेबल', primary: 'प्राथमिक टेबल',
foreign: 'रेफरंस टेबल', foreign: 'रेफरंस टेबल',
cardinality: 'कार्डिनॅलिटी', cardinality: 'कार्डिनॅलिटी',
@@ -198,17 +200,8 @@ export const mr: LanguageTranslation = {
delete_relationship: 'हटवा', delete_relationship: 'हटवा',
}, },
}, },
empty_state: {
title: 'कोणतेही रिलेशनशिप नाहीत',
description:
'टेबल्स कनेक्ट करण्यासाठी एक रिलेशनशिप तयार करा',
},
},
dependencies_section: {
dependencies: 'डिपेंडेन्सि',
filter: 'फिल्टर',
collapse: 'सर्व संकुचित करा',
dependency: { dependency: {
dependency: 'डिपेंडेन्सि',
table: 'टेबल', table: 'टेबल',
dependent_table: 'डिपेंडेन्सि दृश्य', dependent_table: 'डिपेंडेन्सि दृश्य',
delete_dependency: 'हटवा', delete_dependency: 'हटवा',
@@ -218,8 +211,8 @@ export const mr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'कोणत्याही डिपेंडेन्सि नाहीत', title: 'कोणतेही रिलेशनशिप नाहीत',
description: 'सुरू करण्यासाठी एक दृश्य तयार करा', description: 'सुरू करण्यासाठी एक रिलेशनशिप तयार करा',
}, },
}, },
@@ -243,6 +236,35 @@ export const mr: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuals',
tabs: {
areas: 'Areas',
notes: 'नोट्स',
},
},
notes_section: {
filter: 'फिल्टर',
add_note: 'नोट जोडा',
no_results: 'कोणत्याही नोट्स सापडल्या नाहीत',
clear: 'फिल्टर साफ करा',
empty_state: {
title: 'नोट्स नाहीत',
description:
'कॅनव्हासवर मजकूर भाष्य जोडण्यासाठी एक नोट तयार करा',
},
note: {
empty_note: 'रिकामी नोट',
note_actions: {
title: 'नोट क्रिया',
edit_content: 'सामग्री संपादित करा',
delete_note: 'नोट हटवा',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -259,6 +281,7 @@ export const mr: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'कोणतीही enum मूल्ये परिभाषित नाहीत',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -281,7 +304,7 @@ export const mr: LanguageTranslation = {
show_all: 'सर्व दाखवा', show_all: 'सर्व दाखवा',
undo: 'पूर्ववत करा', undo: 'पूर्ववत करा',
redo: 'पुन्हा करा', redo: 'पुन्हा करा',
reorder_diagram: 'आरेख पुनःक्रमित करा', reorder_diagram: 'आरेख स्वयंचलित व्यवस्थित करा',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -321,13 +344,13 @@ export const mr: LanguageTranslation = {
// TODO: Add translations // TODO: Add translations
import_from_file: 'Import from File', import_from_file: 'Import from File',
back: 'मागे', back: 'मागे',
empty_diagram: 'रिक्त आरेख', empty_diagram: 'रिक्त डेटाबेस',
continue: 'सुरू ठेवा', continue: 'सुरू ठेवा',
import: 'आयात करा', import: 'आयात करा',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'आरेख उघडा', title: 'डेटाबेस उघडा',
description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.', description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.',
table_columns: { table_columns: {
name: 'नाव', name: 'नाव',
@@ -337,6 +360,12 @@ export const mr: LanguageTranslation = {
}, },
cancel: 'रद्द करा', cancel: 'रद्द करा',
open: 'उघडा', open: 'उघडा',
diagram_actions: {
open: 'उघडा',
duplicate: 'डुप्लिकेट',
delete: 'हटवा',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -489,9 +518,11 @@ export const mr: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'नवीन टेबल', new_table: 'नवीन टेबल',
new_view: 'नवीन व्ह्यू',
new_relationship: 'नवीन रिलेशनशिप', new_relationship: 'नवीन रिलेशनशिप',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'नवीन टीप',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -512,6 +543,9 @@ export const mr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा बदला', change_language: 'भाषा बदला',
}, },
on: 'चालू',
off: 'बंद',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ne: LanguageTranslation = { export const ne: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'नयाँ',
browse: 'ब्राउज',
tables: 'टेबलहरू',
refs: 'Refs',
dependencies: 'निर्भरताहरू',
custom_types: 'कस्टम प्रकारहरू',
visuals: 'Visuals',
},
menu: { menu: {
file: { actions: {
file: 'फाइल', actions: 'कार्यहरू',
new: 'नयाँ', new: 'नयाँ...',
open: 'खोल्नुहोस्', browse: 'ब्राउज गर्नुहोस्...',
save: 'सुरक्षित गर्नुहोस्', save: 'सुरक्षित गर्नुहोस्',
import: 'डाटाबेस आयात गर्नुहोस्', import: 'डाटाबेस आयात गर्नुहोस्',
export_sql: 'SQL निर्यात गर्नुहोस्', export_sql: 'SQL निर्यात गर्नुहोस्',
export_as: 'निर्यात गर्नुहोस्', export_as: 'निर्यात गर्नुहोस्',
delete_diagram: 'डायाग्राम हटाउनुहोस्', delete_diagram: 'हटाउनुहोस्',
exit: 'बाहिर निस्कनुहोस्',
}, },
edit: { edit: {
edit: 'सम्पादन', edit: 'सम्पादन',
@@ -29,6 +37,7 @@ export const ne: LanguageTranslation = {
hide_field_attributes: 'फिल्ड विशेषताहरू लुकाउनुहोस्', hide_field_attributes: 'फिल्ड विशेषताहरू लुकाउनुहोस्',
show_field_attributes: 'फिल्ड विशेषताहरू देखाउनुहोस्', show_field_attributes: 'फिल्ड विशेषताहरू देखाउनुहोस्',
zoom_on_scroll: 'स्क्रोलमा जुम गर्नुहोस्', zoom_on_scroll: 'स्क्रोलमा जुम गर्नुहोस्',
show_views: 'डाटाबेस भ्यूहरू',
theme: 'थिम', theme: 'थिम',
show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्', show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्',
hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्', hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्',
@@ -66,22 +75,13 @@ export const ne: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'डायाग्राम पुनः क्रमबद्ध गर्नुहोस्', title: 'डायाग्राम स्वचालित मिलाउनुहोस्',
description: description:
'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।', 'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।',
reorder: 'पुनः क्रमबद्ध गर्नुहोस्', reorder: 'स्वचालित मिलाउनुहोस्',
cancel: 'रद्द गर्नुहोस्', cancel: 'रद्द गर्नुहोस्',
}, },
multiple_schemas_alert: {
title: 'विविध स्कीमहरू',
description:
'{{schemasCount}} डायाग्राममा स्कीमहरू। हालको रूपमा देखाइएको छ: {{formattedSchemas}}।',
// TODO: Translate
show_me: 'Show me',
none: 'कुनै पनि छैन',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'प्रतिलिपि असफल', title: 'प्रतिलिपि असफल',
@@ -116,14 +116,11 @@ export const ne: LanguageTranslation = {
copied: 'प्रतिलिपि गरियो!', copied: 'प्रतिलिपि गरियो!',
side_panel: { side_panel: {
schema: 'स्कीम:',
filter_by_schema: 'स्कीम अनुसार फिल्टर गर्नुहोस्',
search_schema: 'स्कीम खोज्नुहोस्...',
no_schemas_found: 'कुनै स्कीमहरू फेला परेनन्',
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्', view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
tables_section: { tables_section: {
tables: 'तालिकाहरू', tables: 'तालिकाहरू',
add_table: 'तालिका थप्नुहोस्', add_table: 'तालिका थप्नुहोस्',
add_view: 'भ्यू थप्नुहोस्',
filter: 'फिल्टर', filter: 'फिल्टर',
collapse: 'सबै लुकाउनुहोस्', collapse: 'सबै लुकाउनुहोस्',
// TODO: Translate // TODO: Translate
@@ -149,6 +146,7 @@ export const ne: LanguageTranslation = {
field_actions: { field_actions: {
title: 'क्षेत्र विशेषताहरू', title: 'क्षेत्र विशेषताहरू',
unique: 'अनन्य', unique: 'अनन्य',
auto_increment: 'स्वचालित वृद्धि',
comments: 'टिप्पणीहरू', comments: 'टिप्पणीहरू',
no_comments: 'कुनै टिप्पणीहरू छैनन्', no_comments: 'कुनै टिप्पणीहरू छैनन्',
delete_field: 'क्षेत्र हटाउनुहोस्', delete_field: 'क्षेत्र हटाउनुहोस्',
@@ -164,6 +162,7 @@ export const ne: LanguageTranslation = {
title: 'सूचक विशेषताहरू', title: 'सूचक विशेषताहरू',
name: 'नाम', name: 'नाम',
unique: 'अनन्य', unique: 'अनन्य',
index_type: 'इन्डेक्स प्रकार',
delete_index: 'सूचक हटाउनुहोस्', delete_index: 'सूचक हटाउनुहोस्',
}, },
table_actions: { table_actions: {
@@ -180,12 +179,15 @@ export const ne: LanguageTranslation = {
description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्', description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'सम्बन्धहरू', refs: 'Refs',
filter: 'फिल्टर', filter: 'फिल्टर',
add_relationship: 'सम्बन्ध थप्नुहोस्',
collapse: 'सबै लुकाउनुहोस्', collapse: 'सबै लुकाउनुहोस्',
add_relationship: 'सम्बन्ध थप्नुहोस्',
relationships: 'सम्बन्धहरू',
dependencies: 'डिपेन्डेन्सीहरू',
relationship: { relationship: {
relationship: 'सम्बन्ध',
primary: 'मुख्य तालिका', primary: 'मुख्य तालिका',
foreign: 'परिचित तालिका', foreign: 'परिचित तालिका',
cardinality: 'कार्डिन्यालिटी', cardinality: 'कार्डिन्यालिटी',
@@ -195,16 +197,8 @@ export const ne: LanguageTranslation = {
delete_relationship: 'हटाउनुहोस्', delete_relationship: 'हटाउनुहोस्',
}, },
}, },
empty_state: {
title: 'कुनै सम्बन्धहरू छैनन्',
description: 'तालिकाहरू जोड्नका लागि एक सम्बन्ध बनाउनुहोस्',
},
},
dependencies_section: {
dependencies: 'डिपेन्डेन्सीहरू',
filter: 'फिल्टर',
collapse: 'सबै लुकाउनुहोस्',
dependency: { dependency: {
dependency: 'डिपेन्डेन्सी',
table: 'तालिका', table: 'तालिका',
dependent_table: 'विचलित तालिका', dependent_table: 'विचलित तालिका',
delete_dependency: 'हटाउनुहोस्', delete_dependency: 'हटाउनुहोस्',
@@ -214,9 +208,8 @@ export const ne: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'कुनै डिपेन्डेन्सीहरू छैनन्', title: 'कुनै सम्बन्धहरू छैनन्',
description: description: 'सुरु गर्नका लागि एक सम्बन्ध बनाउनुहोस्',
'डिपेन्डेन्सीहरू देखाउनका लागि एक व्यू बनाउनुहोस्',
}, },
}, },
@@ -240,6 +233,35 @@ export const ne: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuals',
tabs: {
areas: 'Areas',
notes: 'टिप्पणीहरू',
},
},
notes_section: {
filter: 'फिल्टर',
add_note: 'टिप्पणी थप्नुहोस्',
no_results: 'कुनै टिप्पणी फेला परेन',
clear: 'फिल्टर खाली गर्नुहोस्',
empty_state: {
title: 'कुनै टिप्पणी छैन',
description:
'क्यानभासमा पाठ टिप्पणी थप्न टिप्पणी सिर्जना गर्नुहोस्',
},
note: {
empty_note: 'खाली टिप्पणी',
note_actions: {
title: 'टिप्पणी कार्यहरू',
edit_content: 'सामग्री सम्पादन गर्नुहोस्',
delete_note: 'टिप्पणी मेटाउनुहोस्',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -256,6 +278,7 @@ export const ne: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'कुनै enum मानहरू परिभाषित छैनन्',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -278,7 +301,7 @@ export const ne: LanguageTranslation = {
show_all: 'सबै देखाउनुहोस्', show_all: 'सबै देखाउनुहोस्',
undo: 'पूर्ववत', undo: 'पूर्ववत',
redo: 'पुनः गर्नुहोस्', redo: 'पुनः गर्नुहोस्',
reorder_diagram: 'पुनः क्रमबद्ध गर्नुहोस्', reorder_diagram: 'डायाग्राम स्वचालित मिलाउनुहोस्',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -317,13 +340,13 @@ export const ne: LanguageTranslation = {
cancel: 'रद्द गर्नुहोस्', cancel: 'रद्द गर्नुहोस्',
import_from_file: 'फाइलबाट आयात गर्नुहोस्', import_from_file: 'फाइलबाट आयात गर्नुहोस्',
back: 'फर्क', back: 'फर्क',
empty_diagram: 'रिक्त डायाग्राम', empty_diagram: 'खाली डाटाबेस',
continue: 'जारी राख्नुहोस्', continue: 'जारी राख्नुहोस्',
import: 'आयात गर्नुहोस्', import: 'आयात गर्नुहोस्',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'डायाग्राम खोल्नुहोस्', title: 'डाटाबेस खोल्नुहोस्',
description: description:
'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।', 'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।',
table_columns: { table_columns: {
@@ -334,6 +357,12 @@ export const ne: LanguageTranslation = {
}, },
cancel: 'रद्द गर्नुहोस्', cancel: 'रद्द गर्नुहोस्',
open: 'खोल्नुहोस्', open: 'खोल्नुहोस्',
diagram_actions: {
open: 'खोल्नुहोस्',
duplicate: 'डुप्लिकेट',
delete: 'मेटाउनुहोस्',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -483,9 +512,11 @@ export const ne: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'नयाँ तालिका', new_table: 'नयाँ तालिका',
new_view: 'नयाँ भ्यू',
new_relationship: 'नयाँ सम्बन्ध', new_relationship: 'नयाँ सम्बन्ध',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'नयाँ नोट',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -504,6 +535,9 @@ export const ne: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा परिवर्तन गर्नुहोस्', change_language: 'भाषा परिवर्तन गर्नुहोस्',
}, },
on: 'सक्रिय',
off: 'निष्क्रिय',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const pt_BR: LanguageTranslation = { export const pt_BR: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Novo',
browse: 'Navegar',
tables: 'Tabelas',
refs: 'Refs',
dependencies: 'Dependências',
custom_types: 'Tipos Personalizados',
visuals: 'Visuais',
},
menu: { menu: {
file: { actions: {
file: 'Arquivo', actions: 'Ações',
new: 'Novo', new: 'Novo...',
open: 'Abrir', browse: 'Navegar...',
save: 'Salvar', save: 'Salvar',
import: 'Importar Banco de Dados', import: 'Importar Banco de Dados',
export_sql: 'Exportar SQL', export_sql: 'Exportar SQL',
export_as: 'Exportar como', export_as: 'Exportar como',
delete_diagram: 'Excluir Diagrama', delete_diagram: 'Excluir',
exit: 'Sair',
}, },
edit: { edit: {
edit: 'Editar', edit: 'Editar',
@@ -29,6 +37,7 @@ export const pt_BR: LanguageTranslation = {
hide_field_attributes: 'Ocultar Atributos de Campo', hide_field_attributes: 'Ocultar Atributos de Campo',
show_field_attributes: 'Mostrar Atributos de Campo', show_field_attributes: 'Mostrar Atributos de Campo',
zoom_on_scroll: 'Zoom ao Rolar', zoom_on_scroll: 'Zoom ao Rolar',
show_views: 'Visualizações do Banco de Dados',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Mostrar Dependências', show_dependencies: 'Mostrar Dependências',
hide_dependencies: 'Ocultar Dependências', hide_dependencies: 'Ocultar Dependências',
@@ -66,22 +75,13 @@ export const pt_BR: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Reordenar Diagrama', title: 'Organizar Diagrama Automaticamente',
description: description:
'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?', 'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?',
reorder: 'Reordenar', reorder: 'Organizar Automaticamente',
cancel: 'Cancelar', cancel: 'Cancelar',
}, },
multiple_schemas_alert: {
title: 'Múltiplos Esquemas',
description:
'{{schemasCount}} esquemas neste diagrama. Atualmente exibindo: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'nenhum',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Falha na cópia', title: 'Falha na cópia',
@@ -116,14 +116,11 @@ export const pt_BR: LanguageTranslation = {
copied: 'Copiado!', copied: 'Copiado!',
side_panel: { side_panel: {
schema: 'Esquema:',
filter_by_schema: 'Filtrar por esquema',
search_schema: 'Buscar esquema...',
no_schemas_found: 'Nenhum esquema encontrado.',
view_all_options: 'Ver todas as Opções...', view_all_options: 'Ver todas as Opções...',
tables_section: { tables_section: {
tables: 'Tabelas', tables: 'Tabelas',
add_table: 'Adicionar Tabela', add_table: 'Adicionar Tabela',
add_view: 'Adicionar Visualização',
filter: 'Filtrar', filter: 'Filtrar',
collapse: 'Colapsar Todas', collapse: 'Colapsar Todas',
// TODO: Translate // TODO: Translate
@@ -149,6 +146,7 @@ export const pt_BR: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Atributos do Campo', title: 'Atributos do Campo',
unique: 'Único', unique: 'Único',
auto_increment: 'Incremento Automático',
comments: 'Comentários', comments: 'Comentários',
no_comments: 'Sem comentários', no_comments: 'Sem comentários',
delete_field: 'Excluir Campo', delete_field: 'Excluir Campo',
@@ -164,6 +162,7 @@ export const pt_BR: LanguageTranslation = {
title: 'Atributos do Índice', title: 'Atributos do Índice',
name: 'Nome', name: 'Nome',
unique: 'Único', unique: 'Único',
index_type: 'Tipo de Índice',
delete_index: 'Excluir Índice', delete_index: 'Excluir Índice',
}, },
table_actions: { table_actions: {
@@ -180,12 +179,15 @@ export const pt_BR: LanguageTranslation = {
description: 'Crie uma tabela para começar', description: 'Crie uma tabela para começar',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relacionamentos', refs: 'Refs',
filter: 'Filtrar', filter: 'Filtrar',
add_relationship: 'Adicionar Relacionamento',
collapse: 'Colapsar Todas', collapse: 'Colapsar Todas',
add_relationship: 'Adicionar Relacionamento',
relationships: 'Relacionamentos',
dependencies: 'Dependências',
relationship: { relationship: {
relationship: 'Relacionamento',
primary: 'Tabela Primária', primary: 'Tabela Primária',
foreign: 'Tabela Referenciada', foreign: 'Tabela Referenciada',
cardinality: 'Cardinalidade', cardinality: 'Cardinalidade',
@@ -195,16 +197,8 @@ export const pt_BR: LanguageTranslation = {
delete_relationship: 'Excluir', delete_relationship: 'Excluir',
}, },
}, },
empty_state: {
title: 'Sem relacionamentos',
description: 'Crie um relacionamento para conectar tabelas',
},
},
dependencies_section: {
dependencies: 'Dependências',
filter: 'Filtrar',
collapse: 'Colapsar Todas',
dependency: { dependency: {
dependency: 'Dependência',
table: 'Tabela', table: 'Tabela',
dependent_table: 'Visualização Dependente', dependent_table: 'Visualização Dependente',
delete_dependency: 'Excluir', delete_dependency: 'Excluir',
@@ -214,8 +208,8 @@ export const pt_BR: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Sem dependências', title: 'Sem relacionamentos',
description: 'Crie uma visualização para começar', description: 'Crie um relacionamento para começar',
}, },
}, },
@@ -239,6 +233,35 @@ export const pt_BR: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuais',
tabs: {
areas: 'Areas',
notes: 'Notas',
},
},
notes_section: {
filter: 'Filtrar',
add_note: 'Adicionar Nota',
no_results: 'Nenhuma nota encontrada',
clear: 'Limpar Filtro',
empty_state: {
title: 'Sem Notas',
description:
'Crie uma nota para adicionar anotações de texto na tela',
},
note: {
empty_note: 'Nota vazia',
note_actions: {
title: 'Ações de Nota',
edit_content: 'Editar Conteúdo',
delete_note: 'Excluir Nota',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -255,6 +278,7 @@ export const pt_BR: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Nenhum valor de enum definido',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -277,7 +301,7 @@ export const pt_BR: LanguageTranslation = {
show_all: 'Mostrar Tudo', show_all: 'Mostrar Tudo',
undo: 'Desfazer', undo: 'Desfazer',
redo: 'Refazer', redo: 'Refazer',
reorder_diagram: 'Reordenar Diagrama', reorder_diagram: 'Organizar Diagrama Automaticamente',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -316,13 +340,13 @@ export const pt_BR: LanguageTranslation = {
back: 'Voltar', back: 'Voltar',
// TODO: Translate // TODO: Translate
import_from_file: 'Import from File', import_from_file: 'Import from File',
empty_diagram: 'Diagrama vazio', empty_diagram: 'Banco de dados vazio',
continue: 'Continuar', continue: 'Continuar',
import: 'Importar', import: 'Importar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Abrir Diagrama', title: 'Abrir Banco de Dados',
description: 'Selecione um diagrama para abrir da lista abaixo.', description: 'Selecione um diagrama para abrir da lista abaixo.',
table_columns: { table_columns: {
name: 'Nome', name: 'Nome',
@@ -332,6 +356,12 @@ export const pt_BR: LanguageTranslation = {
}, },
cancel: 'Cancelar', cancel: 'Cancelar',
open: 'Abrir', open: 'Abrir',
diagram_actions: {
open: 'Abrir',
duplicate: 'Duplicar',
delete: 'Excluir',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -481,9 +511,11 @@ export const pt_BR: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Nova Tabela', new_table: 'Nova Tabela',
new_view: 'Nova Visualização',
new_relationship: 'Novo Relacionamento', new_relationship: 'Novo Relacionamento',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Nova Nota',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -503,6 +535,9 @@ export const pt_BR: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Idioma', change_language: 'Idioma',
}, },
on: 'Ligado',
off: 'Desligado',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ru: LanguageTranslation = { export const ru: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Новая',
browse: 'Обзор',
tables: 'Таблицы',
refs: 'Ссылки',
dependencies: 'Зависимости',
custom_types: 'Пользовательские типы',
visuals: 'Визуальные элементы',
},
menu: { menu: {
file: { actions: {
file: 'Файл', actions: 'Действия',
new: 'Создать', new: 'Новая...',
open: 'Открыть', browse: 'Обзор...',
save: 'Сохранить', save: 'Сохранить',
import: 'Импортировать базу данных', import: 'Импортировать базу данных',
export_sql: 'Экспорт SQL', export_sql: 'Экспорт SQL',
export_as: 'Экспортировать как', export_as: 'Экспортировать как',
delete_diagram: 'Удалить диаграмму', delete_diagram: 'Удалить',
exit: 'Выход',
}, },
edit: { edit: {
edit: 'Изменение', edit: 'Изменение',
@@ -29,6 +37,7 @@ export const ru: LanguageTranslation = {
show_field_attributes: 'Показать атрибуты поля', show_field_attributes: 'Показать атрибуты поля',
hide_field_attributes: 'Скрыть атрибуты поля', hide_field_attributes: 'Скрыть атрибуты поля',
zoom_on_scroll: 'Увеличение при прокрутке', zoom_on_scroll: 'Увеличение при прокрутке',
show_views: 'Представления базы данных',
theme: 'Тема', theme: 'Тема',
show_dependencies: 'Показать зависимости', show_dependencies: 'Показать зависимости',
hide_dependencies: 'Скрыть зависимости', hide_dependencies: 'Скрыть зависимости',
@@ -64,22 +73,13 @@ export const ru: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Переупорядочить диаграмму', title: 'Автоматическая расстановка диаграммы',
description: description:
'Это действие переставит все таблицы на диаграмме. Хотите продолжить?', 'Это действие переставит все таблицы на диаграмме. Хотите продолжить?',
reorder: 'Изменить порядок', reorder: 'Автоматическая расстановка',
cancel: 'Отменить', cancel: 'Отменить',
}, },
multiple_schemas_alert: {
title: 'Множественные схемы',
description:
'{{schemasCount}} схем в этой диаграмме. В данный момент отображается: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'никто',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Ошибка копирования', title: 'Ошибка копирования',
@@ -113,14 +113,11 @@ export const ru: LanguageTranslation = {
show_less: 'Показать меньше', show_less: 'Показать меньше',
side_panel: { side_panel: {
schema: 'Схема:',
filter_by_schema: 'Фильтр по схеме',
search_schema: 'Схема поиска...',
no_schemas_found: 'Схемы не найдены.',
view_all_options: 'Просмотреть все варианты...', view_all_options: 'Просмотреть все варианты...',
tables_section: { tables_section: {
tables: 'Таблицы', tables: 'Таблицы',
add_table: 'Добавить таблицу', add_table: 'Добавить таблицу',
add_view: 'Добавить представление',
filter: 'Фильтр', filter: 'Фильтр',
collapse: 'Свернуть все', collapse: 'Свернуть все',
clear: 'Очистить фильтр', clear: 'Очистить фильтр',
@@ -146,6 +143,7 @@ export const ru: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Атрибуты поля', title: 'Атрибуты поля',
unique: 'Уникальный', unique: 'Уникальный',
auto_increment: 'Автоинкремент',
comments: 'Комментарии', comments: 'Комментарии',
no_comments: 'Нет комментария', no_comments: 'Нет комментария',
delete_field: 'Удалить поле', delete_field: 'Удалить поле',
@@ -160,6 +158,7 @@ export const ru: LanguageTranslation = {
title: 'Атрибуты индекса', title: 'Атрибуты индекса',
name: 'Имя', name: 'Имя',
unique: 'Уникальный', unique: 'Уникальный',
index_type: 'Тип индекса',
delete_index: 'Удалить индекс', delete_index: 'Удалить индекс',
}, },
table_actions: { table_actions: {
@@ -176,12 +175,15 @@ export const ru: LanguageTranslation = {
description: 'Создайте таблицу, чтобы начать', description: 'Создайте таблицу, чтобы начать',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Отношения', refs: 'Ссылки',
filter: 'Фильтр', filter: 'Фильтр',
add_relationship: 'Добавить отношение',
collapse: 'Свернуть все', collapse: 'Свернуть все',
add_relationship: 'Добавить отношение',
relationships: 'Отношения',
dependencies: 'Зависимости',
relationship: { relationship: {
relationship: 'Отношение',
primary: 'Основная таблица', primary: 'Основная таблица',
foreign: 'Справочная таблица', foreign: 'Справочная таблица',
cardinality: 'Тип множественной связи', cardinality: 'Тип множественной связи',
@@ -191,18 +193,10 @@ export const ru: LanguageTranslation = {
delete_relationship: 'Удалить', delete_relationship: 'Удалить',
}, },
}, },
empty_state: {
title: 'Нет отношений',
description: 'Создайте связь для соединения таблиц',
},
},
dependencies_section: {
dependencies: 'Зависимости',
filter: 'Фильтр',
collapse: 'Свернуть все',
dependency: { dependency: {
table: 'Стол', dependency: 'Зависимость',
dependent_table: 'Зависимый вид', table: 'Таблица',
dependent_table: 'Зависимое представление',
delete_dependency: 'Удалить', delete_dependency: 'Удалить',
dependency_actions: { dependency_actions: {
title: 'Действия', title: 'Действия',
@@ -210,8 +204,8 @@ export const ru: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Нет зависимостей', title: 'Нет отношений',
description: 'Создайте представление, чтобы начать', description: 'Создайте отношение, чтобы начать',
}, },
}, },
@@ -236,6 +230,35 @@ export const ru: LanguageTranslation = {
description: 'Создайте область, чтобы начать', description: 'Создайте область, чтобы начать',
}, },
}, },
visuals_section: {
visuals: 'Визуальные элементы',
tabs: {
areas: 'Области',
notes: 'Заметки',
},
},
notes_section: {
filter: 'Фильтр',
add_note: 'Добавить Заметку',
no_results: 'Заметки не найдены',
clear: 'Очистить Фильтр',
empty_state: {
title: 'Нет Заметок',
description:
'Создайте заметку, чтобы добавить текстовые аннотации на холсте',
},
note: {
empty_note: 'Пустая заметка',
note_actions: {
title: 'Действия с Заметкой',
edit_content: 'Редактировать Содержимое',
delete_note: 'Удалить Заметку',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -252,6 +275,7 @@ export const ru: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Значения перечисления не определены',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -274,7 +298,7 @@ export const ru: LanguageTranslation = {
show_all: 'Показать все', show_all: 'Показать все',
undo: 'Отменить', undo: 'Отменить',
redo: 'Вернуть', redo: 'Вернуть',
reorder_diagram: 'Переупорядочить диаграмму', reorder_diagram: 'Автоматическая расстановка диаграммы',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -312,13 +336,13 @@ export const ru: LanguageTranslation = {
cancel: 'Отменить', cancel: 'Отменить',
back: 'Назад', back: 'Назад',
import_from_file: 'Импортировать из файла', import_from_file: 'Импортировать из файла',
empty_diagram: 'Пустая диаграмма', empty_diagram: 'Пустая база данных',
continue: 'Продолжить', continue: 'Продолжить',
import: 'Импорт', import: 'Импорт',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Открыть диаграмму', title: 'Открыть базу данных',
description: description:
'Выберите диаграмму, которую нужно открыть, из списка ниже.', 'Выберите диаграмму, которую нужно открыть, из списка ниже.',
table_columns: { table_columns: {
@@ -329,6 +353,12 @@ export const ru: LanguageTranslation = {
}, },
cancel: 'Отмена', cancel: 'Отмена',
open: 'Открыть', open: 'Открыть',
diagram_actions: {
open: 'Открыть',
duplicate: 'Дублировать',
delete: 'Удалить',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -477,8 +507,10 @@ export const ru: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Создать таблицу', new_table: 'Создать таблицу',
new_view: 'Новое представление',
new_relationship: 'Создать отношение', new_relationship: 'Создать отношение',
new_area: 'Новая область', new_area: 'Новая область',
new_note: 'Новая Заметка',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -498,6 +530,9 @@ export const ru: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Сменить язык', change_language: 'Сменить язык',
}, },
on: 'Вкл',
off: 'Выкл',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const te: LanguageTranslation = { export const te: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'కొత్తది',
browse: 'బ్రాఉజ్',
tables: 'టేబల్లు',
refs: 'సంబంధాలు',
dependencies: 'ఆధారతలు',
custom_types: 'కస్టమ్ టైప్స్',
visuals: 'Visuals',
},
menu: { menu: {
file: { actions: {
file: 'ఫైల్', actions: 'చర్యలు',
new: 'కొత్తది', new: 'కొత్తది...',
open: 'తెరవు', browse: 'బ్రాఉజ్ చేయండి...',
save: 'సేవ్', save: 'సేవ్',
import: 'డేటాబేస్‌ను దిగుమతి చేసుకోండి', import: 'డేటాబేస్‌ను దిగుమతి చేసుకోండి',
export_sql: 'SQL ఎగుమతి', export_sql: 'SQL ఎగుమతి',
export_as: 'వగా ఎగుమతి చేయండి', export_as: 'వగా ఎగుమతి చేయండి',
delete_diagram: 'చిత్రాన్ని తొలగించండి', delete_diagram: 'తొలగించండి',
exit: 'నిష్క్రమించు',
}, },
edit: { edit: {
edit: 'సవరించు', edit: 'సవరించు',
@@ -29,6 +37,7 @@ export const te: LanguageTranslation = {
show_field_attributes: 'ఫీల్డ్ గుణాలను చూపించు', show_field_attributes: 'ఫీల్డ్ గుణాలను చూపించు',
hide_field_attributes: 'ఫీల్డ్ గుణాలను దాచండి', hide_field_attributes: 'ఫీల్డ్ గుణాలను దాచండి',
zoom_on_scroll: 'స్క్రోల్‌పై జూమ్', zoom_on_scroll: 'స్క్రోల్‌పై జూమ్',
show_views: 'డేటాబేస్ వ్యూలు',
theme: 'థీమ్', theme: 'థీమ్',
show_dependencies: 'ఆధారాలు చూపించండి', show_dependencies: 'ఆధారాలు చూపించండి',
hide_dependencies: 'ఆధారాలను దాచండి', hide_dependencies: 'ఆధారాలను దాచండి',
@@ -66,22 +75,13 @@ export const te: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'చిత్రాన్ని పునఃసరిచేయండి', title: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
description: description:
'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?', 'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?',
reorder: 'పునఃసరిచేయండి', reorder: 'స్వయంచాలకంగా అమర్చండి',
cancel: 'రద్దు', cancel: 'రద్దు',
}, },
multiple_schemas_alert: {
title: 'బహుళ స్కీమాలు',
description:
'{{schemasCount}} స్కీమాలు ఈ చిత్రంలో ఉన్నాయి. ప్రస్తుత స్కీమాలు: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'ఎదరికాదు',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'కాపీ విఫలమైంది', title: 'కాపీ విఫలమైంది',
@@ -116,14 +116,11 @@ export const te: LanguageTranslation = {
copied: 'కాపీ చేయబడింది!', copied: 'కాపీ చేయబడింది!',
side_panel: { side_panel: {
schema: 'స్కీమా:',
filter_by_schema: 'స్కీమా ద్వారా ఫిల్టర్ చేయండి',
search_schema: 'స్కీమా కోసం శోధించండి...',
no_schemas_found: 'ఏ స్కీమాలు కూడా కనుగొనబడలేదు.',
view_all_options: 'అన్ని ఎంపికలను చూడండి...', view_all_options: 'అన్ని ఎంపికలను చూడండి...',
tables_section: { tables_section: {
tables: 'పట్టికలు', tables: 'పట్టికలు',
add_table: 'పట్టికను జోడించు', add_table: 'పట్టికను జోడించు',
add_view: 'వ్యూ జోడించండి',
filter: 'ఫిల్టర్', filter: 'ఫిల్టర్',
collapse: 'అన్ని కూల్ చేయి', collapse: 'అన్ని కూల్ చేయి',
// TODO: Translate // TODO: Translate
@@ -149,6 +146,7 @@ export const te: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ఫీల్డ్ గుణాలు', title: 'ఫీల్డ్ గుణాలు',
unique: 'అద్వితీయ', unique: 'అద్వితీయ',
auto_increment: 'ఆటో ఇంక్రిమెంట్',
comments: 'వ్యాఖ్యలు', comments: 'వ్యాఖ్యలు',
no_comments: 'వ్యాఖ్యలు లేవు', no_comments: 'వ్యాఖ్యలు లేవు',
delete_field: 'ఫీల్డ్ తొలగించు', delete_field: 'ఫీల్డ్ తొలగించు',
@@ -164,6 +162,7 @@ export const te: LanguageTranslation = {
title: 'ఇండెక్స్ గుణాలు', title: 'ఇండెక్స్ గుణాలు',
name: 'పేరు', name: 'పేరు',
unique: 'అద్వితీయ', unique: 'అద్వితీయ',
index_type: 'ఇండెక్స్ రకం',
delete_index: 'ఇండెక్స్ తొలగించు', delete_index: 'ఇండెక్స్ తొలగించు',
}, },
table_actions: { table_actions: {
@@ -181,12 +180,15 @@ export const te: LanguageTranslation = {
description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి', description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'సంబంధాలు', refs: 'Refs',
filter: 'ఫిల్టర్', filter: 'ఫిల్టర్',
add_relationship: 'సంబంధం జోడించు',
collapse: 'అన్ని కూల్ చేయి', collapse: 'అన్ని కూల్ చేయి',
add_relationship: 'సంబంధం జోడించు',
relationships: 'సంబంధాలు',
dependencies: 'ఆధారాలు',
relationship: { relationship: {
relationship: 'సంబంధం',
primary: 'ప్రాథమిక పట్టిక', primary: 'ప్రాథమిక పట్టిక',
foreign: 'సూచించబడిన పట్టిక', foreign: 'సూచించబడిన పట్టిక',
cardinality: 'కార్డినాలిటీ', cardinality: 'కార్డినాలిటీ',
@@ -196,16 +198,8 @@ export const te: LanguageTranslation = {
delete_relationship: 'సంబంధం తొలగించు', delete_relationship: 'సంబంధం తొలగించు',
}, },
}, },
empty_state: {
title: 'సంబంధాలు లేవు',
description: 'పట్టికలను అనుసంధించడానికి సంబంధం సృష్టించండి',
},
},
dependencies_section: {
dependencies: 'ఆధారాలు',
filter: 'ఫిల్టర్',
collapse: 'అన్ని కూల్ చేయి',
dependency: { dependency: {
dependency: 'ఆధారం',
table: 'పట్టిక', table: 'పట్టిక',
dependent_table: 'ఆధారిత వీక్షణ', dependent_table: 'ఆధారిత వీక్షణ',
delete_dependency: 'ఆధారాన్ని తొలగించు', delete_dependency: 'ఆధారాన్ని తొలగించు',
@@ -215,8 +209,8 @@ export const te: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'ఆధారాలు లేవు', title: 'సంబంధాలు లేవు',
description: 'ప్రారంభించడానికి ఒక వీక్షణ సృష్టించండి', description: 'ప్రారంభించడానికి ఒక సంబంధం సృష్టించండి',
}, },
}, },
@@ -240,6 +234,35 @@ export const te: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Visuals',
tabs: {
areas: 'Areas',
notes: 'గమనికలు',
},
},
notes_section: {
filter: 'ఫిల్టర్',
add_note: 'గమనిక జోడించండి',
no_results: 'గమనికలు కనుగొనబడలేదు',
clear: 'ఫిల్టర్‌ను క్లియర్ చేయండి',
empty_state: {
title: 'గమనికలు లేవు',
description:
'కాన్వాస్‌పై టెక్స్ట్ ఉల్లేఖనలను జోడించడానికి ఒక గమనికను సృష్టించండి',
},
note: {
empty_note: 'ఖాళీ గమనిక',
note_actions: {
title: 'గమనిక చర్యలు',
edit_content: 'కంటెంట్‌ను సవరించండి',
delete_note: 'గమనికను తొలగించండి',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -256,6 +279,7 @@ export const te: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'ఏ enum విలువలు నిర్వచించబడలేదు',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -278,7 +302,7 @@ export const te: LanguageTranslation = {
show_all: 'అన్ని చూపించు', show_all: 'అన్ని చూపించు',
undo: 'తిరిగి చేయు', undo: 'తిరిగి చేయు',
redo: 'మరలా చేయు', redo: 'మరలా చేయు',
reorder_diagram: 'చిత్రాన్ని పునఃసరిచేయండి', reorder_diagram: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -317,13 +341,13 @@ export const te: LanguageTranslation = {
// TODO: Translate // TODO: Translate
import_from_file: 'Import from File', import_from_file: 'Import from File',
back: 'తిరుగు', back: 'తిరుగు',
empty_diagram: 'ఖాళీ చిత్రము', empty_diagram: 'ఖాళీ డేటాబేస్',
continue: 'కొనసాగించు', continue: 'కొనసాగించు',
import: 'డిగుమతి', import: 'డిగుమతి',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'చిత్రం తెరవండి', title: 'డేటాబేస్ తెరవండి',
description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.', description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.',
table_columns: { table_columns: {
name: 'పేరు', name: 'పేరు',
@@ -333,6 +357,12 @@ export const te: LanguageTranslation = {
}, },
cancel: 'రద్దు', cancel: 'రద్దు',
open: 'తెరవు', open: 'తెరవు',
diagram_actions: {
open: 'తెరవు',
duplicate: 'నకలు',
delete: 'తొలగించు',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -485,9 +515,11 @@ export const te: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'కొత్త పట్టిక', new_table: 'కొత్త పట్టిక',
new_view: 'కొత్త వ్యూ',
new_relationship: 'కొత్త సంబంధం', new_relationship: 'కొత్త సంబంధం',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'కొత్త నోట్',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -508,6 +540,9 @@ export const te: LanguageTranslation = {
language_select: { language_select: {
change_language: 'భాష మార్చు', change_language: 'భాష మార్చు',
}, },
on: 'ఆన్',
off: 'ఆఫ్',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const tr: LanguageTranslation = { export const tr: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Yeni',
browse: 'Gözat',
tables: 'Tablolar',
refs: 'Refs',
dependencies: 'Bağımlılıklar',
custom_types: 'Özel Tipler',
visuals: 'Görseller',
},
menu: { menu: {
file: { actions: {
file: 'Dosya', actions: 'Eylemler',
new: 'Yeni', new: 'Yeni...',
open: '', browse: 'Gözat...',
save: 'Kaydet', save: 'Kaydet',
import: 'Veritabanı İçe Aktar', import: 'Veritabanı İçe Aktar',
export_sql: 'SQL Olarak Dışa Aktar', export_sql: 'SQL Olarak Dışa Aktar',
export_as: 'Olarak Dışa Aktar', export_as: 'Olarak Dışa Aktar',
delete_diagram: 'Diyagramı Sil', delete_diagram: 'Sil',
exit: ıkış',
}, },
edit: { edit: {
edit: 'Düzenle', edit: 'Düzenle',
@@ -29,6 +37,7 @@ export const tr: LanguageTranslation = {
show_field_attributes: 'Alan Özelliklerini Göster', show_field_attributes: 'Alan Özelliklerini Göster',
hide_field_attributes: 'Alan Özelliklerini Gizle', hide_field_attributes: 'Alan Özelliklerini Gizle',
zoom_on_scroll: 'Kaydırarak Yakınlaştır', zoom_on_scroll: 'Kaydırarak Yakınlaştır',
show_views: 'Veritabanı Görünümleri',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Bağımlılıkları Göster', show_dependencies: 'Bağımlılıkları Göster',
hide_dependencies: 'Bağımlılıkları Gizle', hide_dependencies: 'Bağımlılıkları Gizle',
@@ -66,22 +75,13 @@ export const tr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Diyagramı Yeniden Sırala', title: 'Diyagramı Otomatik Düzenle',
description: description:
'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?', 'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?',
reorder: 'Yeniden Sırala', reorder: 'Otomatik Düzenle',
cancel: 'İptal', cancel: 'İptal',
}, },
multiple_schemas_alert: {
title: 'Birden Fazla Şema',
description:
'Bu diyagramda {{schemasCount}} şema var. Şu anda görüntülenen: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'yok',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Kopyalama başarısız', title: 'Kopyalama başarısız',
@@ -115,14 +115,11 @@ export const tr: LanguageTranslation = {
copy_to_clipboard: 'Panoya Kopyala', copy_to_clipboard: 'Panoya Kopyala',
copied: 'Kopyalandı!', copied: 'Kopyalandı!',
side_panel: { side_panel: {
schema: 'Şema:',
filter_by_schema: 'Şemaya Göre Filtrele',
search_schema: 'Şema ara...',
no_schemas_found: 'Şema bulunamadı.',
view_all_options: 'Tüm Seçenekleri Gör...', view_all_options: 'Tüm Seçenekleri Gör...',
tables_section: { tables_section: {
tables: 'Tablolar', tables: 'Tablolar',
add_table: 'Tablo Ekle', add_table: 'Tablo Ekle',
add_view: 'Görünüm Ekle',
filter: 'Filtrele', filter: 'Filtrele',
collapse: 'Hepsini Daralt', collapse: 'Hepsini Daralt',
// TODO: Translate // TODO: Translate
@@ -148,6 +145,7 @@ export const tr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Alan Özellikleri', title: 'Alan Özellikleri',
unique: 'Tekil', unique: 'Tekil',
auto_increment: 'Otomatik Artış',
comments: 'Yorumlar', comments: 'Yorumlar',
no_comments: 'Yorum yok', no_comments: 'Yorum yok',
delete_field: 'Alanı Sil', delete_field: 'Alanı Sil',
@@ -163,6 +161,7 @@ export const tr: LanguageTranslation = {
title: 'İndeks Özellikleri', title: 'İndeks Özellikleri',
name: 'Ad', name: 'Ad',
unique: 'Tekil', unique: 'Tekil',
index_type: 'İndeks Türü',
delete_index: 'İndeksi Sil', delete_index: 'İndeksi Sil',
}, },
table_actions: { table_actions: {
@@ -180,12 +179,15 @@ export const tr: LanguageTranslation = {
description: 'Başlamak için bir tablo oluşturun', description: 'Başlamak için bir tablo oluşturun',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'İlişkiler', refs: 'Refs',
filter: 'Filtrele', filter: 'Filtrele',
add_relationship: 'İlişki Ekle',
collapse: 'Hepsini Daralt', collapse: 'Hepsini Daralt',
add_relationship: 'İlişki Ekle',
relationships: 'İlişkiler',
dependencies: 'Bağımlılıklar',
relationship: { relationship: {
relationship: 'İlişki',
primary: 'Birincil Tablo', primary: 'Birincil Tablo',
foreign: 'Referans Tablo', foreign: 'Referans Tablo',
cardinality: 'Kardinalite', cardinality: 'Kardinalite',
@@ -195,16 +197,8 @@ export const tr: LanguageTranslation = {
delete_relationship: 'Sil', delete_relationship: 'Sil',
}, },
}, },
empty_state: {
title: 'İlişki yok',
description: 'Tabloları bağlamak için bir ilişki oluşturun',
},
},
dependencies_section: {
dependencies: 'Bağımlılıklar',
filter: 'Filtrele',
collapse: 'Hepsini Daralt',
dependency: { dependency: {
dependency: 'Bağımlılık',
table: 'Tablo', table: 'Tablo',
dependent_table: 'Bağımlı Görünüm', dependent_table: 'Bağımlı Görünüm',
delete_dependency: 'Sil', delete_dependency: 'Sil',
@@ -214,8 +208,8 @@ export const tr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Bağımlılık yok', title: 'İlişki yok',
description: 'Başlamak için bir görünüm oluşturun', description: 'Başlamak için bir ilişki oluşturun',
}, },
}, },
@@ -239,6 +233,35 @@ export const tr: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Görseller',
tabs: {
areas: 'Areas',
notes: 'Notlar',
},
},
notes_section: {
filter: 'Filtrele',
add_note: 'Not Ekle',
no_results: 'Not bulunamadı',
clear: 'Filtreyi Temizle',
empty_state: {
title: 'Not Yok',
description:
'Tuval üzerinde metin açıklamaları eklemek için bir not oluşturun',
},
note: {
empty_note: 'Boş not',
note_actions: {
title: 'Not İşlemleri',
edit_content: 'İçeriği Düzenle',
delete_note: 'Notu Sil',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -255,6 +278,7 @@ export const tr: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Tanımlanmış enum değeri yok',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -276,7 +300,7 @@ export const tr: LanguageTranslation = {
show_all: 'Hepsini Gör', show_all: 'Hepsini Gör',
undo: 'Geri Al', undo: 'Geri Al',
redo: 'Yinele', redo: 'Yinele',
reorder_diagram: 'Diyagramı Yeniden Sırala', reorder_diagram: 'Diyagramı Otomatik Düzenle',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -313,12 +337,12 @@ export const tr: LanguageTranslation = {
import_from_file: 'Import from File', import_from_file: 'Import from File',
cancel: 'İptal', cancel: 'İptal',
back: 'Geri', back: 'Geri',
empty_diagram: 'Boş diyagram', empty_diagram: 'Boş veritabanı',
continue: 'Devam', continue: 'Devam',
import: 'İçe Aktar', import: 'İçe Aktar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Diyagramı Aç', title: 'Veritabanı Aç',
description: 'Aşağıdaki listeden açmak için bir diyagram seçin.', description: 'Aşağıdaki listeden açmak için bir diyagram seçin.',
table_columns: { table_columns: {
name: 'Ad', name: 'Ad',
@@ -328,6 +352,12 @@ export const tr: LanguageTranslation = {
}, },
cancel: 'İptal', cancel: 'İptal',
open: 'Aç', open: 'Aç',
diagram_actions: {
open: 'Aç',
duplicate: 'Kopyala',
delete: 'Sil',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -470,9 +500,11 @@ export const tr: LanguageTranslation = {
}, },
canvas_context_menu: { canvas_context_menu: {
new_table: 'Yeni Tablo', new_table: 'Yeni Tablo',
new_view: 'Yeni Görünüm',
new_relationship: 'Yeni İlişki', new_relationship: 'Yeni İlişki',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Yeni Not',
}, },
table_node_context_menu: { table_node_context_menu: {
edit_table: 'Tabloyu Düzenle', edit_table: 'Tabloyu Düzenle',
@@ -492,6 +524,9 @@ export const tr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Dil', change_language: 'Dil',
}, },
on: 'Açık',
off: 'Kapalı',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const uk: LanguageTranslation = { export const uk: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Нова',
browse: 'Огляд',
tables: 'Таблиці',
refs: 'Зв’язки',
dependencies: 'Залежності',
custom_types: 'Користувацькі типи',
visuals: 'Візуальні елементи',
},
menu: { menu: {
file: { actions: {
file: 'Файл', actions: 'Дії',
new: 'Новий', new: 'Нова...',
open: 'Відкрити', browse: 'Огляд...',
save: 'Зберегти', save: 'Зберегти',
import: 'Імпорт бази даних', import: 'Імпорт бази даних',
export_sql: 'Експорт SQL', export_sql: 'Експорт SQL',
export_as: 'Експортувати як', export_as: 'Експортувати як',
delete_diagram: 'Видалити діаграму', delete_diagram: 'Видалити',
exit: 'Вийти',
}, },
edit: { edit: {
edit: 'Редагувати', edit: 'Редагувати',
@@ -29,6 +37,7 @@ export const uk: LanguageTranslation = {
show_field_attributes: 'Показати атрибути полів', show_field_attributes: 'Показати атрибути полів',
hide_field_attributes: 'Приховати атрибути полів', hide_field_attributes: 'Приховати атрибути полів',
zoom_on_scroll: 'Масштабувати прокручуванням', zoom_on_scroll: 'Масштабувати прокручуванням',
show_views: 'Представлення бази даних',
theme: 'Тема', theme: 'Тема',
show_dependencies: 'Показати залежності', show_dependencies: 'Показати залежності',
hide_dependencies: 'Приховати залежності', hide_dependencies: 'Приховати залежності',
@@ -64,22 +73,13 @@ export const uk: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Перевпорядкувати діаграму', title: 'Автоматичне розміщення діаграми',
description: description:
'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?', 'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?',
reorder: 'Перевпорядкувати', reorder: 'Автоматичне розміщення',
cancel: 'Скасувати', cancel: 'Скасувати',
}, },
multiple_schemas_alert: {
title: 'Кілька схем',
description:
'{{schemasCount}} схеми на цій діаграмі. Зараз відображається: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'немає',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Помилка копіювання', title: 'Помилка копіювання',
@@ -114,14 +114,11 @@ export const uk: LanguageTranslation = {
copied: 'Скопійовано!', copied: 'Скопійовано!',
side_panel: { side_panel: {
schema: 'Схема:',
filter_by_schema: 'Фільтрувати за схемою',
search_schema: 'Пошук схеми…',
no_schemas_found: 'Схеми не знайдено.',
view_all_options: 'Переглянути всі параметри…', view_all_options: 'Переглянути всі параметри…',
tables_section: { tables_section: {
tables: 'Таблиці', tables: 'Таблиці',
add_table: 'Додати таблицю', add_table: 'Додати таблицю',
add_view: 'Додати представлення',
filter: 'Фільтр', filter: 'Фільтр',
collapse: 'Згорнути все', collapse: 'Згорнути все',
// TODO: Translate // TODO: Translate
@@ -147,6 +144,7 @@ export const uk: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Атрибути полів', title: 'Атрибути полів',
unique: 'Унікальне', unique: 'Унікальне',
auto_increment: 'Автоінкремент',
comments: 'Коментарі', comments: 'Коментарі',
no_comments: 'Немає коментарів', no_comments: 'Немає коментарів',
delete_field: 'Видалити поле', delete_field: 'Видалити поле',
@@ -162,6 +160,7 @@ export const uk: LanguageTranslation = {
title: 'Атрибути індексу', title: 'Атрибути індексу',
name: 'Назва індекса', name: 'Назва індекса',
unique: 'Унікальний', unique: 'Унікальний',
index_type: 'Тип індексу',
delete_index: 'Видалити індекс', delete_index: 'Видалити індекс',
}, },
table_actions: { table_actions: {
@@ -178,12 +177,15 @@ export const uk: LanguageTranslation = {
description: 'Щоб почати, створіть таблицю', description: 'Щоб почати, створіть таблицю',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Звʼязки', refs: 'Refs',
filter: 'Фільтр', filter: 'Фільтр',
add_relationship: 'Додати звʼязок',
collapse: 'Згорнути все', collapse: 'Згорнути все',
add_relationship: 'Додати звʼязок',
relationships: 'Звʼязки',
dependencies: 'Залежності',
relationship: { relationship: {
relationship: 'Звʼязок',
primary: 'Первинна таблиця', primary: 'Первинна таблиця',
foreign: 'Посилання на таблицю', foreign: 'Посилання на таблицю',
cardinality: 'Звʼязок', cardinality: 'Звʼязок',
@@ -193,16 +195,8 @@ export const uk: LanguageTranslation = {
delete_relationship: 'Видалити', delete_relationship: 'Видалити',
}, },
}, },
empty_state: {
title: 'Звʼязків немає',
description: 'Створіть звʼязок для зʼєднання таблиць',
},
},
dependencies_section: {
dependencies: 'Залежності',
filter: 'Фільтр',
collapse: 'Згорнути все',
dependency: { dependency: {
dependency: 'Залежність',
table: 'Таблиця', table: 'Таблиця',
dependent_table: 'Залежне подання', dependent_table: 'Залежне подання',
delete_dependency: 'Видалити', delete_dependency: 'Видалити',
@@ -212,8 +206,8 @@ export const uk: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Жодних залежностей', title: 'Жодних зв’язків',
description: 'Створіть подання, щоб почати', description: 'Створіть зв’язок, щоб почати',
}, },
}, },
@@ -237,6 +231,35 @@ export const uk: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Візуальні елементи',
tabs: {
areas: 'Areas',
notes: 'Нотатки',
},
},
notes_section: {
filter: 'Фільтр',
add_note: 'Додати Нотатку',
no_results: 'Нотатки не знайдено',
clear: 'Очистити Фільтр',
empty_state: {
title: 'Немає Нотаток',
description:
'Створіть нотатку, щоб додати текстові анотації на полотні',
},
note: {
empty_note: 'Порожня нотатка',
note_actions: {
title: 'Дії з Нотаткою',
edit_content: 'Редагувати Вміст',
delete_note: 'Видалити Нотатку',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -253,6 +276,7 @@ export const uk: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Значення переліку не визначені',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -275,7 +299,7 @@ export const uk: LanguageTranslation = {
show_all: 'Показати все', show_all: 'Показати все',
undo: 'Скасувати', undo: 'Скасувати',
redo: 'Повторити', redo: 'Повторити',
reorder_diagram: 'Перевпорядкувати діаграму', reorder_diagram: 'Автоматичне розміщення діаграми',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -313,13 +337,13 @@ export const uk: LanguageTranslation = {
cancel: 'Скасувати', cancel: 'Скасувати',
back: 'Назад', back: 'Назад',
import_from_file: 'Імпортувати з файлу', import_from_file: 'Імпортувати з файлу',
empty_diagram: 'Порожня діаграма', empty_diagram: 'Порожня база даних',
continue: 'Продовжити', continue: 'Продовжити',
import: 'Імпорт', import: 'Імпорт',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Відкрити діаграму', title: 'Відкрити базу даних',
description: description:
'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.', 'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.',
table_columns: { table_columns: {
@@ -330,6 +354,12 @@ export const uk: LanguageTranslation = {
}, },
cancel: 'Скасувати', cancel: 'Скасувати',
open: 'Відкрити', open: 'Відкрити',
diagram_actions: {
open: 'Відкрити',
duplicate: 'Дублювати',
delete: 'Видалити',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -476,9 +506,11 @@ export const uk: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Нова таблиця', new_table: 'Нова таблиця',
new_view: 'Нове представлення',
new_relationship: 'Новий звʼязок', new_relationship: 'Новий звʼязок',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Нова Нотатка',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -497,6 +529,9 @@ export const uk: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Мова', change_language: 'Мова',
}, },
on: 'Увімк',
off: 'Вимк',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const vi: LanguageTranslation = { export const vi: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Mới',
browse: 'Duyệt',
tables: 'Bảng',
refs: 'Refs',
dependencies: 'Phụ thuộc',
custom_types: 'Kiểu tùy chỉnh',
visuals: 'Hình ảnh',
},
menu: { menu: {
file: { actions: {
file: 'Tệp', actions: 'Hành động',
new: 'Tạo mới', new: 'Mới...',
open: 'Mở', browse: 'Duyệt...',
save: 'Lưu', save: 'Lưu',
import: 'Nhập cơ sở dữ liệu', import: 'Nhập cơ sở dữ liệu',
export_sql: 'Xuất SQL', export_sql: 'Xuất SQL',
export_as: 'Xuất thành', export_as: 'Xuất thành',
delete_diagram: 'Xóa sơ đồ', delete_diagram: 'Xóa',
exit: 'Thoát',
}, },
edit: { edit: {
edit: 'Sửa', edit: 'Sửa',
@@ -29,6 +37,7 @@ export const vi: LanguageTranslation = {
show_field_attributes: 'Hiển thị thuộc tính trường', show_field_attributes: 'Hiển thị thuộc tính trường',
hide_field_attributes: 'Ẩn thuộc tính trường', hide_field_attributes: 'Ẩn thuộc tính trường',
zoom_on_scroll: 'Thu phóng khi cuộn', zoom_on_scroll: 'Thu phóng khi cuộn',
show_views: 'Chế độ xem Cơ sở dữ liệu',
theme: 'Chủ đề', theme: 'Chủ đề',
show_dependencies: 'Hiển thị các phụ thuộc', show_dependencies: 'Hiển thị các phụ thuộc',
hide_dependencies: 'Ẩn các phụ thuộc', hide_dependencies: 'Ẩn các phụ thuộc',
@@ -65,22 +74,13 @@ export const vi: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Sắp xếp lại sơ đồ', title: 'Tự động sắp xếp sơ đồ',
description: description:
'Hành động này sẽ sắp xếp lại tất cả các bảng trong sơ đồ. Bạn có muốn tiếp tục không?', 'Hành động này sẽ sắp xếp lại tất cả các bảng trong sơ đồ. Bạn có muốn tiếp tục không?',
reorder: 'Sắp xếp', reorder: 'Tự động sắp xếp',
cancel: 'Hủy', cancel: 'Hủy',
}, },
multiple_schemas_alert: {
title: 'Có nhiều lược đồ',
description:
'Có {{schemasCount}} lược đồ trong sơ đồ này. Hiện đang hiển thị: {{formattedSchemas}}.',
// TODO: Translate
show_me: 'Show me',
none: 'không có',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Sao chép thất bại', title: 'Sao chép thất bại',
@@ -115,14 +115,11 @@ export const vi: LanguageTranslation = {
copied: 'Đã sao chép!', copied: 'Đã sao chép!',
side_panel: { side_panel: {
schema: 'Lược đồ:',
filter_by_schema: 'Lọc bởi lược đồ',
search_schema: 'Tìm kiếm lược đồ...',
no_schemas_found: 'Không tìm thấy lược đồ.',
view_all_options: 'Xem tất cả tùy chọn...', view_all_options: 'Xem tất cả tùy chọn...',
tables_section: { tables_section: {
tables: 'Bảng', tables: 'Bảng',
add_table: 'Thêm bảng', add_table: 'Thêm bảng',
add_view: 'Thêm Chế độ xem',
filter: 'Lọc', filter: 'Lọc',
collapse: 'Thu gọn tất cả', collapse: 'Thu gọn tất cả',
// TODO: Translate // TODO: Translate
@@ -148,6 +145,7 @@ export const vi: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Thuộc tính trường', title: 'Thuộc tính trường',
unique: 'Giá trị duy nhất', unique: 'Giá trị duy nhất',
auto_increment: 'Tự động tăng',
comments: 'Bình luận', comments: 'Bình luận',
no_comments: 'Không có bình luận', no_comments: 'Không có bình luận',
delete_field: 'Xóa trường', delete_field: 'Xóa trường',
@@ -163,6 +161,7 @@ export const vi: LanguageTranslation = {
title: 'Thuộc tính chỉ mục', title: 'Thuộc tính chỉ mục',
name: 'Tên', name: 'Tên',
unique: 'Giá trị duy nhất', unique: 'Giá trị duy nhất',
index_type: 'Loại chỉ mục',
delete_index: 'Xóa chỉ mục', delete_index: 'Xóa chỉ mục',
}, },
table_actions: { table_actions: {
@@ -179,12 +178,15 @@ export const vi: LanguageTranslation = {
description: 'Tạo một bảng để bắt đầu', description: 'Tạo một bảng để bắt đầu',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Quan hệ', refs: 'Refs',
filter: 'Lọc', filter: 'Lọc',
add_relationship: 'Thêm quan hệ',
collapse: 'Thu gọn tất cả', collapse: 'Thu gọn tất cả',
add_relationship: 'Thêm quan hệ',
relationships: 'Quan hệ',
dependencies: 'Phụ thuộc',
relationship: { relationship: {
relationship: 'Quan hệ',
primary: 'Bảng khóa chính', primary: 'Bảng khóa chính',
foreign: 'Bảng khóa ngoại', foreign: 'Bảng khóa ngoại',
cardinality: 'Quan hệ', cardinality: 'Quan hệ',
@@ -194,16 +196,8 @@ export const vi: LanguageTranslation = {
delete_relationship: 'Xóa', delete_relationship: 'Xóa',
}, },
}, },
empty_state: {
title: 'Không có quan hệ',
description: 'Tạo quan hệ để kết nối các bảng',
},
},
dependencies_section: {
dependencies: 'Phụ thuộc',
filter: 'Lọc',
collapse: 'Thu gọn tất cả',
dependency: { dependency: {
dependency: 'Phụ thuộc',
table: 'Bảng', table: 'Bảng',
dependent_table: 'Bảng xem phụ thuộc', dependent_table: 'Bảng xem phụ thuộc',
delete_dependency: 'Xóa', delete_dependency: 'Xóa',
@@ -213,8 +207,8 @@ export const vi: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Không có phụ thuộc', title: 'Không có quan hệ',
description: 'Tạo bảng xem phụ thuộc để bắt đầu', description: 'Tạo một quan hệ để bắt đầu',
}, },
}, },
@@ -238,6 +232,35 @@ export const vi: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: 'Hình ảnh',
tabs: {
areas: 'Areas',
notes: 'Ghi chú',
},
},
notes_section: {
filter: 'Lọc',
add_note: 'Thêm Ghi Chú',
no_results: 'Không tìm thấy ghi chú',
clear: 'Xóa Bộ Lọc',
empty_state: {
title: 'Không Có Ghi Chú',
description:
'Tạo ghi chú để thêm chú thích văn bản trên canvas',
},
note: {
empty_note: 'Ghi chú trống',
note_actions: {
title: 'Hành Động Ghi Chú',
edit_content: 'Chỉnh Sửa Nội Dung',
delete_note: 'Xóa Ghi Chú',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -254,6 +277,7 @@ export const vi: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Không có giá trị enum được định nghĩa',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -276,7 +300,7 @@ export const vi: LanguageTranslation = {
show_all: 'Hiển thị tất cả', show_all: 'Hiển thị tất cả',
undo: 'Hoàn tác', undo: 'Hoàn tác',
redo: 'Làm lại', redo: 'Làm lại',
reorder_diagram: 'Sắp xếp lại sơ đồ', reorder_diagram: 'Tự động sắp xếp sơ đồ',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -314,13 +338,13 @@ export const vi: LanguageTranslation = {
cancel: 'Hủy', cancel: 'Hủy',
import_from_file: 'Nhập từ tệp', import_from_file: 'Nhập từ tệp',
back: 'Trở lại', back: 'Trở lại',
empty_diagram: 'Sơ đồ trống', empty_diagram: 'Cơ sở dữ liệu trống',
continue: 'Tiếp tục', continue: 'Tiếp tục',
import: 'Nhập', import: 'Nhập',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Mở sơ đồ', title: 'Mở cơ sở dữ liệu',
description: 'Chọn sơ đồ để mở từ danh sách bên dưới.', description: 'Chọn sơ đồ để mở từ danh sách bên dưới.',
table_columns: { table_columns: {
name: 'Tên', name: 'Tên',
@@ -330,6 +354,12 @@ export const vi: LanguageTranslation = {
}, },
cancel: 'Hủy', cancel: 'Hủy',
open: 'Mở', open: 'Mở',
diagram_actions: {
open: 'Mở',
duplicate: 'Nhân bản',
delete: 'Xóa',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -477,9 +507,11 @@ export const vi: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Tạo bảng mới', new_table: 'Tạo bảng mới',
new_view: 'Chế độ xem Mới',
new_relationship: 'Tạo quan hệ mới', new_relationship: 'Tạo quan hệ mới',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: 'Ghi Chú Mới',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -498,6 +530,9 @@ export const vi: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Ngôn ngữ', change_language: 'Ngôn ngữ',
}, },
on: 'Bật',
off: 'Tắt',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const zh_CN: LanguageTranslation = { export const zh_CN: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '新建',
browse: '浏览',
tables: '表',
refs: '引用',
dependencies: '依赖关系',
custom_types: '自定义类型',
visuals: '视觉效果',
},
menu: { menu: {
file: { actions: {
file: '文件', actions: '操作',
new: '新建', new: '新建...',
open: '打开', browse: '浏览...',
save: '保存', save: '保存',
import: '导入数据库', import: '导入数据库',
export_sql: '导出 SQL 语句', export_sql: '导出 SQL 语句',
export_as: '导出为', export_as: '导出为',
delete_diagram: '删除关系图', delete_diagram: '删除',
exit: '退出',
}, },
edit: { edit: {
edit: '编辑', edit: '编辑',
@@ -29,6 +37,7 @@ export const zh_CN: LanguageTranslation = {
show_field_attributes: '展示字段属性', show_field_attributes: '展示字段属性',
hide_field_attributes: '隐藏字段属性', hide_field_attributes: '隐藏字段属性',
zoom_on_scroll: '滚动缩放', zoom_on_scroll: '滚动缩放',
show_views: '数据库视图',
theme: '主题', theme: '主题',
show_dependencies: '展示依赖', show_dependencies: '展示依赖',
hide_dependencies: '隐藏依赖', hide_dependencies: '隐藏依赖',
@@ -63,21 +72,12 @@ export const zh_CN: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: '重新排列关系图', title: '自动排列关系图',
description: '此操作将重新排列关系图中的所有表。是否要继续?', description: '此操作将重新排列关系图中的所有表。是否要继续?',
reorder: '重新排列', reorder: '自动排列',
cancel: '取消', cancel: '取消',
}, },
multiple_schemas_alert: {
title: '多个模式',
description:
'此关系图中有 {{schemasCount}} 个模式,当前显示:{{formattedSchemas}}。',
// TODO: Translate
show_me: 'Show me',
none: '无',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: '复制失败', title: '复制失败',
@@ -112,14 +112,11 @@ export const zh_CN: LanguageTranslation = {
copied: '复制了!', copied: '复制了!',
side_panel: { side_panel: {
schema: '模式:',
filter_by_schema: '按模式筛选',
search_schema: '搜索模式...',
no_schemas_found: '未找到模式。',
view_all_options: '查看所有选项...', view_all_options: '查看所有选项...',
tables_section: { tables_section: {
tables: '表', tables: '表',
add_table: '添加表', add_table: '添加表',
add_view: '添加视图',
filter: '筛选', filter: '筛选',
collapse: '全部折叠', collapse: '全部折叠',
// TODO: Translate // TODO: Translate
@@ -145,6 +142,7 @@ export const zh_CN: LanguageTranslation = {
field_actions: { field_actions: {
title: '字段属性', title: '字段属性',
unique: '唯一', unique: '唯一',
auto_increment: '自动递增',
comments: '注释', comments: '注释',
no_comments: '空', no_comments: '空',
delete_field: '删除字段', delete_field: '删除字段',
@@ -160,6 +158,7 @@ export const zh_CN: LanguageTranslation = {
title: '索引属性', title: '索引属性',
name: '名称', name: '名称',
unique: '唯一', unique: '唯一',
index_type: '索引类型',
delete_index: '删除索引', delete_index: '删除索引',
}, },
table_actions: { table_actions: {
@@ -176,12 +175,15 @@ export const zh_CN: LanguageTranslation = {
description: '新建表以开始', description: '新建表以开始',
}, },
}, },
relationships_section: { refs_section: {
relationships: '关系', refs: '引用',
filter: '筛选', filter: '筛选',
add_relationship: '添加关系',
collapse: '全部折叠', collapse: '全部折叠',
add_relationship: '添加关系',
relationships: '关系',
dependencies: '依赖关系',
relationship: { relationship: {
relationship: '关系',
primary: '主表', primary: '主表',
foreign: '被引用表', foreign: '被引用表',
cardinality: '基数', cardinality: '基数',
@@ -191,16 +193,8 @@ export const zh_CN: LanguageTranslation = {
delete_relationship: '删除', delete_relationship: '删除',
}, },
}, },
empty_state: {
title: '无关系',
description: '创建关系以连接表',
},
},
dependencies_section: {
dependencies: '依赖关系',
filter: '筛选',
collapse: '全部折叠',
dependency: { dependency: {
dependency: '依赖',
table: '表', table: '表',
dependent_table: '依赖视图', dependent_table: '依赖视图',
delete_dependency: '删除', delete_dependency: '删除',
@@ -210,8 +204,8 @@ export const zh_CN: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '无依赖', title: '无关系',
description: '创建视图以开始', description: '创建关系以开始',
}, },
}, },
@@ -235,6 +229,34 @@ export const zh_CN: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: '视觉效果',
tabs: {
areas: 'Areas',
notes: '笔记',
},
},
notes_section: {
filter: '筛选',
add_note: '添加笔记',
no_results: '未找到笔记',
clear: '清除筛选',
empty_state: {
title: '没有笔记',
description: '创建笔记以在画布上添加文本注释',
},
note: {
empty_note: '空笔记',
note_actions: {
title: '笔记操作',
edit_content: '编辑内容',
delete_note: '删除笔记',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -251,6 +273,7 @@ export const zh_CN: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '没有定义枚举值',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -273,7 +296,7 @@ export const zh_CN: LanguageTranslation = {
show_all: '展示全部', show_all: '展示全部',
undo: '撤销', undo: '撤销',
redo: '重做', redo: '重做',
reorder_diagram: '重新排列关系图', reorder_diagram: '自动排列关系图',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -311,13 +334,13 @@ export const zh_CN: LanguageTranslation = {
cancel: '取消', cancel: '取消',
import_from_file: '从文件导入', import_from_file: '从文件导入',
back: '上一步', back: '上一步',
empty_diagram: '新建空关系图', empty_diagram: '空数据库',
continue: '下一步', continue: '下一步',
import: '导入', import: '导入',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: '打开关系图', title: '打开数据库',
description: '从下面的列表中选择一个图表打开。', description: '从下面的列表中选择一个图表打开。',
table_columns: { table_columns: {
name: '名称', name: '名称',
@@ -327,6 +350,12 @@ export const zh_CN: LanguageTranslation = {
}, },
cancel: '取消', cancel: '取消',
open: '打开', open: '打开',
diagram_actions: {
open: '打开',
duplicate: '复制',
delete: '删除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -472,9 +501,11 @@ export const zh_CN: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '新建表', new_table: '新建表',
new_view: '新建视图',
new_relationship: '新建关系', new_relationship: '新建关系',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: '新笔记',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -493,6 +524,9 @@ export const zh_CN: LanguageTranslation = {
language_select: { language_select: {
change_language: '语言', change_language: '语言',
}, },
on: '开启',
off: '关闭',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const zh_TW: LanguageTranslation = { export const zh_TW: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '新建',
browse: '瀏覽',
tables: '表格',
refs: 'Refs',
dependencies: '相依性',
custom_types: '自定義類型',
visuals: '視覺效果',
},
menu: { menu: {
file: { actions: {
file: '檔案', actions: '操作',
new: '新增', new: '新增...',
open: '開啟', browse: '瀏覽...',
save: '儲存', save: '儲存',
import: '匯入資料庫', import: '匯入資料庫',
export_sql: '匯出 SQL', export_sql: '匯出 SQL',
export_as: '匯出為特定格式', export_as: '匯出為特定格式',
delete_diagram: '刪除圖表', delete_diagram: '刪除',
exit: '退出',
}, },
edit: { edit: {
edit: '編輯', edit: '編輯',
@@ -29,6 +37,7 @@ export const zh_TW: LanguageTranslation = {
hide_field_attributes: '隱藏欄位屬性', hide_field_attributes: '隱藏欄位屬性',
show_field_attributes: '顯示欄位屬性', show_field_attributes: '顯示欄位屬性',
zoom_on_scroll: '滾動縮放', zoom_on_scroll: '滾動縮放',
show_views: '資料庫檢視',
theme: '主題', theme: '主題',
show_dependencies: '顯示相依性', show_dependencies: '顯示相依性',
hide_dependencies: '隱藏相依性', hide_dependencies: '隱藏相依性',
@@ -63,21 +72,12 @@ export const zh_TW: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: '重新排列圖表', title: '自動排列圖表',
description: '此操作將重新排列圖表中的所有表格。是否繼續?', description: '此操作將重新排列圖表中的所有表格。是否繼續?',
reorder: '重新排列', reorder: '自動排列',
cancel: '取消', cancel: '取消',
}, },
multiple_schemas_alert: {
title: '多重 Schema',
description:
'此圖表中包含 {{schemasCount}} 個 Schema目前顯示{{formattedSchemas}}。',
// TODO: Translate
show_me: 'Show me',
none: '無',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: '複製失敗', title: '複製失敗',
@@ -112,14 +112,11 @@ export const zh_TW: LanguageTranslation = {
copied: '已複製!', copied: '已複製!',
side_panel: { side_panel: {
schema: 'Schema:',
filter_by_schema: '依 Schema 篩選',
search_schema: '搜尋 Schema...',
no_schemas_found: '未找到 Schema。',
view_all_options: '顯示所有選項...', view_all_options: '顯示所有選項...',
tables_section: { tables_section: {
tables: '表格', tables: '表格',
add_table: '新增表格', add_table: '新增表格',
add_view: '新增檢視',
filter: '篩選', filter: '篩選',
collapse: '全部摺疊', collapse: '全部摺疊',
// TODO: Translate // TODO: Translate
@@ -145,6 +142,7 @@ export const zh_TW: LanguageTranslation = {
field_actions: { field_actions: {
title: '欄位屬性', title: '欄位屬性',
unique: '唯一', unique: '唯一',
auto_increment: '自動遞增',
comments: '註解', comments: '註解',
no_comments: '無註解', no_comments: '無註解',
delete_field: '刪除欄位', delete_field: '刪除欄位',
@@ -160,6 +158,7 @@ export const zh_TW: LanguageTranslation = {
title: '索引屬性', title: '索引屬性',
name: '名稱', name: '名稱',
unique: '唯一', unique: '唯一',
index_type: '索引類型',
delete_index: '刪除索引', delete_index: '刪除索引',
}, },
table_actions: { table_actions: {
@@ -176,12 +175,15 @@ export const zh_TW: LanguageTranslation = {
description: '請新增表格以開始', description: '請新增表格以開始',
}, },
}, },
relationships_section: { refs_section: {
relationships: '關聯', refs: 'Refs',
filter: '篩選', filter: '篩選',
add_relationship: '新增關聯',
collapse: '全部摺疊', collapse: '全部摺疊',
add_relationship: '新增關聯',
relationships: '關聯',
dependencies: '相依性',
relationship: { relationship: {
relationship: '關聯',
primary: '主表格', primary: '主表格',
foreign: '參照表格', foreign: '參照表格',
cardinality: '基數', cardinality: '基數',
@@ -191,16 +193,8 @@ export const zh_TW: LanguageTranslation = {
delete_relationship: '刪除', delete_relationship: '刪除',
}, },
}, },
empty_state: {
title: '尚無關聯',
description: '請新增關聯以連接表格',
},
},
dependencies_section: {
dependencies: '相依性',
filter: '篩選',
collapse: '全部摺疊',
dependency: { dependency: {
dependency: '相依性',
table: '表格', table: '表格',
dependent_table: '相依檢視', dependent_table: '相依檢視',
delete_dependency: '刪除', delete_dependency: '刪除',
@@ -210,8 +204,8 @@ export const zh_TW: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '尚無相依性', title: '尚無關聯',
description: '請建立檢視以開始', description: '請建立關聯以開始',
}, },
}, },
@@ -235,6 +229,34 @@ export const zh_TW: LanguageTranslation = {
description: 'Create an area to get started', description: 'Create an area to get started',
}, },
}, },
visuals_section: {
visuals: '視覺效果',
tabs: {
areas: 'Areas',
notes: '筆記',
},
},
notes_section: {
filter: '篩選',
add_note: '新增筆記',
no_results: '未找到筆記',
clear: '清除篩選',
empty_state: {
title: '沒有筆記',
description: '建立筆記以在畫布上新增文字註解',
},
note: {
empty_note: '空白筆記',
note_actions: {
title: '筆記操作',
edit_content: '編輯內容',
delete_note: '刪除筆記',
},
},
},
// TODO: Translate // TODO: Translate
custom_types_section: { custom_types_section: {
custom_types: 'Custom Types', custom_types: 'Custom Types',
@@ -251,6 +273,7 @@ export const zh_TW: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '沒有定義列舉值',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
@@ -273,7 +296,7 @@ export const zh_TW: LanguageTranslation = {
show_all: '顯示全部', show_all: '顯示全部',
undo: '復原', undo: '復原',
redo: '重做', redo: '重做',
reorder_diagram: '重新排列圖表', reorder_diagram: '自動排列圖表',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip: custom_type_highlight_tooltip:
@@ -310,13 +333,13 @@ export const zh_TW: LanguageTranslation = {
cancel: '取消', cancel: '取消',
import_from_file: '從檔案匯入', import_from_file: '從檔案匯入',
back: '返回', back: '返回',
empty_diagram: '空白圖表', empty_diagram: '空資料庫',
continue: '繼續', continue: '繼續',
import: '匯入', import: '匯入',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: '開啟圖表', title: '開啟資料庫',
description: '請從以下列表中選擇一個圖表。', description: '請從以下列表中選擇一個圖表。',
table_columns: { table_columns: {
name: '名稱', name: '名稱',
@@ -326,6 +349,12 @@ export const zh_TW: LanguageTranslation = {
}, },
cancel: '取消', cancel: '取消',
open: '開啟', open: '開啟',
diagram_actions: {
open: '開啟',
duplicate: '複製',
delete: '刪除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -472,9 +501,11 @@ export const zh_TW: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '新建表格', new_table: '新建表格',
new_view: '新檢視',
new_relationship: '新建關聯', new_relationship: '新建關聯',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
new_note: '新筆記',
}, },
table_node_context_menu: { table_node_context_menu: {
@@ -493,6 +524,9 @@ export const zh_TW: LanguageTranslation = {
language_select: { language_select: {
change_language: '變更語言', change_language: '變更語言',
}, },
on: '開啟',
off: '關閉',
}, },
}; };

View File

@@ -18,4 +18,7 @@
.marker-definitions { .marker-definitions {
} }
.nodrag {
}
} }

View File

@@ -6,6 +6,7 @@ import type { DBIndex } from './domain/db-index';
import type { DBRelationship } from './domain/db-relationship'; import type { DBRelationship } from './domain/db-relationship';
import type { DBTable } from './domain/db-table'; import type { DBTable } from './domain/db-table';
import type { Diagram } from './domain/diagram'; import type { Diagram } from './domain/diagram';
import type { Note } from './domain/note';
import { generateId as defaultGenerateId } from './utils'; import { generateId as defaultGenerateId } from './utils';
const generateIdsMapFromTable = ( const generateIdsMapFromTable = (
@@ -49,6 +50,10 @@ const generateIdsMapFromDiagram = (
idsMap.set(area.id, generateId()); idsMap.set(area.id, generateId());
}); });
diagram.notes?.forEach((note) => {
idsMap.set(note.id, generateId());
});
diagram.customTypes?.forEach((customType) => { diagram.customTypes?.forEach((customType) => {
idsMap.set(customType.id, generateId()); idsMap.set(customType.id, generateId());
}); });
@@ -218,6 +223,21 @@ export const cloneDiagram = (
}) })
.filter((area): area is Area => area !== null) ?? []; .filter((area): area is Area => area !== null) ?? [];
const notes: Note[] =
diagram.notes
?.map((note) => {
const id = getNewId(note.id);
if (!id) {
return null;
}
return {
...note,
id,
} satisfies Note;
})
.filter((note): note is Note => note !== null) ?? [];
const customTypes: DBCustomType[] = const customTypes: DBCustomType[] =
diagram.customTypes diagram.customTypes
?.map((customType) => { ?.map((customType) => {
@@ -242,6 +262,7 @@ export const cloneDiagram = (
relationships, relationships,
tables, tables,
areas, areas,
notes,
customTypes, customTypes,
createdAt: diagram.createdAt createdAt: diagram.createdAt
? new Date(diagram.createdAt) ? new Date(diagram.createdAt)

View File

@@ -19,3 +19,5 @@ export const randomColor = () => {
export const viewColor = '#b0b0b0'; export const viewColor = '#b0b0b0';
export const materializedViewColor = '#7d7d7d'; export const materializedViewColor = '#7d7d7d';
export const defaultTableColor = '#8eb7ff';
export const defaultAreaColor = '#b067e9';

View File

@@ -129,9 +129,6 @@ export const clickhouseDataTypes: readonly DataTypeData[] = [
{ name: 'enum', id: 'enum' }, { name: 'enum', id: 'enum' },
{ name: 'lowcardinality', id: 'lowcardinality' }, { name: 'lowcardinality', id: 'lowcardinality' },
// Array Type
{ name: 'array', id: 'array' },
// Tuple Type // Tuple Type
{ name: 'tuple', id: 'tuple' }, { name: 'tuple', id: 'tuple' },
{ name: 'map', id: 'map' }, { name: 'map', id: 'map' },

View File

@@ -1,5 +1,6 @@
import { z } from 'zod'; import { z } from 'zod';
import { DatabaseType } from '../../domain/database-type'; import { DatabaseType } from '../../domain/database-type';
import { databaseSupportsArrays } from '../../domain/database-capabilities';
import { clickhouseDataTypes } from './clickhouse-data-types'; import { clickhouseDataTypes } from './clickhouse-data-types';
import { genericDataTypes } from './generic-data-types'; import { genericDataTypes } from './generic-data-types';
import { mariadbDataTypes } from './mariadb-data-types'; import { mariadbDataTypes } from './mariadb-data-types';
@@ -146,3 +147,53 @@ export const findDataTypeDataById = (
return dataTypesOptions.find((dataType) => dataType.id === id); return dataTypesOptions.find((dataType) => dataType.id === id);
}; };
export const supportsAutoIncrementDataType = (
dataTypeName: string
): boolean => {
return [
'integer',
'int',
'bigint',
'smallint',
'tinyint',
'mediumint',
'serial',
'bigserial',
'smallserial',
'number',
'numeric',
'decimal',
].includes(dataTypeName.toLocaleLowerCase());
};
export const autoIncrementAlwaysOn = (dataTypeName: string): boolean => {
return ['serial', 'bigserial', 'smallserial'].includes(
dataTypeName.toLowerCase()
);
};
export const requiresNotNull = (dataTypeName: string): boolean => {
return ['serial', 'bigserial', 'smallserial'].includes(
dataTypeName.toLowerCase()
);
};
const ARRAY_INCOMPATIBLE_TYPES = [
'serial',
'bigserial',
'smallserial',
] as const;
export const supportsArrayDataType = (
dataTypeName: string,
databaseType: DatabaseType
): boolean => {
if (!databaseSupportsArrays(databaseType)) {
return false;
}
return !ARRAY_INCOMPATIBLE_TYPES.includes(
dataTypeName.toLowerCase() as (typeof ARRAY_INCOMPATIBLE_TYPES)[number]
);
};

View File

@@ -12,6 +12,7 @@ export const postgresDataTypes: readonly DataTypeData[] = [
{ name: 'text', id: 'text', usageLevel: 1 }, { name: 'text', id: 'text', usageLevel: 1 },
{ name: 'boolean', id: 'boolean', usageLevel: 1 }, { name: 'boolean', id: 'boolean', usageLevel: 1 },
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 }, { name: 'timestamp', id: 'timestamp', usageLevel: 1 },
{ name: 'timestamptz', id: 'timestamptz', usageLevel: 1 },
{ name: 'date', id: 'date', usageLevel: 1 }, { name: 'date', id: 'date', usageLevel: 1 },
// Level 2 - Second most common types // Level 2 - Second most common types
@@ -42,6 +43,7 @@ export const postgresDataTypes: readonly DataTypeData[] = [
id: 'timestamp_with_time_zone', id: 'timestamp_with_time_zone',
usageLevel: 2, usageLevel: 2,
}, },
{ name: 'int', id: 'int', usageLevel: 2 },
// Less common types // Less common types
{ {
@@ -95,7 +97,6 @@ export const postgresDataTypes: readonly DataTypeData[] = [
{ name: 'tsvector', id: 'tsvector' }, { name: 'tsvector', id: 'tsvector' },
{ name: 'tsquery', id: 'tsquery' }, { name: 'tsquery', id: 'tsquery' },
{ name: 'xml', id: 'xml' }, { name: 'xml', id: 'xml' },
{ name: 'array', id: 'array' },
{ name: 'int4range', id: 'int4range' }, { name: 'int4range', id: 'int4range' },
{ name: 'int8range', id: 'int8range' }, { name: 'int8range', id: 'int8range' },
{ name: 'numrange', id: 'numrange' }, { name: 'numrange', id: 'numrange' },

View File

@@ -50,5 +50,8 @@ export const sqliteDataTypes: readonly DataTypeData[] = [
{ name: 'smallint', id: 'smallint' }, { name: 'smallint', id: 'smallint' },
{ name: 'bigint', id: 'bigint' }, { name: 'bigint', id: 'bigint' },
{ name: 'bool', id: 'bool' }, { name: 'bool', id: 'bool' },
{ name: 'boolean', id: 'boolean' }, // Added for smartquery compatibility
{ name: 'time', id: 'time' }, { name: 'time', id: 'time' },
{ name: 'date', id: 'date' }, // Added for smartquery compatibility
{ name: 'datetime', id: 'datetime' }, // Added for smartquery compatibility
] as const; ] as const;

View File

@@ -0,0 +1,21 @@
import type { DBCustomType, DBCustomTypeKind } from '@/lib/domain';
import { schemaNameToDomainSchemaName } from '@/lib/domain';
import type { DBCustomTypeInfo } from '../metadata-types/custom-type-info';
import { generateId } from '@/lib/utils';
export const createCustomTypesFromMetadata = ({
customTypes,
}: {
customTypes: DBCustomTypeInfo[];
}): DBCustomType[] => {
return customTypes.map((customType) => {
return {
id: generateId(),
schema: schemaNameToDomainSchemaName(customType.schema),
name: customType.type,
kind: customType.kind as DBCustomTypeKind,
values: customType.values,
fields: customType.fields,
};
});
};

View File

@@ -0,0 +1,351 @@
import { generateId } from '@/lib/utils';
import type { AST } from 'node-sql-parser';
import type { DBDependency, DBTable } from '@/lib/domain';
import { DatabaseType, schemaNameToDomainSchemaName } from '@/lib/domain';
import type { ViewInfo } from '../metadata-types/view-info';
import { decodeViewDefinition } from './tables';
const astDatabaseTypes: Record<DatabaseType, string> = {
[DatabaseType.POSTGRESQL]: 'postgresql',
[DatabaseType.MYSQL]: 'postgresql',
[DatabaseType.MARIADB]: 'postgresql',
[DatabaseType.GENERIC]: 'postgresql',
[DatabaseType.SQLITE]: 'postgresql',
[DatabaseType.SQL_SERVER]: 'postgresql',
[DatabaseType.CLICKHOUSE]: 'postgresql',
[DatabaseType.COCKROACHDB]: 'postgresql',
[DatabaseType.ORACLE]: 'postgresql',
};
export const createDependenciesFromMetadata = async ({
views,
tables,
databaseType,
}: {
views: ViewInfo[];
tables: DBTable[];
databaseType: DatabaseType;
}): Promise<DBDependency[]> => {
if (!views || views.length === 0) {
return [];
}
const { Parser } = await import('node-sql-parser');
const parser = new Parser();
const dependencies = views
.flatMap((view) => {
const viewSchema = schemaNameToDomainSchemaName(view.schema);
const viewTable = tables.find(
(table) =>
table.name === view.view_name && viewSchema === table.schema
);
if (!viewTable) {
console.warn(
`Source table for view ${view.view_name} not found (schema: ${viewSchema})`
);
return []; // Skip this view and proceed to the next
}
if (view.view_definition) {
try {
const decodedViewDefinition = decodeViewDefinition(
databaseType,
view.view_definition
);
let modifiedViewDefinition = '';
if (
databaseType === DatabaseType.MYSQL ||
databaseType === DatabaseType.MARIADB
) {
modifiedViewDefinition = preprocessViewDefinitionMySQL(
decodedViewDefinition
);
} else if (databaseType === DatabaseType.SQL_SERVER) {
modifiedViewDefinition =
preprocessViewDefinitionSQLServer(
decodedViewDefinition
);
} else {
modifiedViewDefinition = preprocessViewDefinition(
decodedViewDefinition
);
}
// Parse using the appropriate dialect
const ast = parser.astify(modifiedViewDefinition, {
database: astDatabaseTypes[databaseType],
type: 'select', // Parsing a SELECT statement
});
let relatedTables = extractTablesFromAST(ast);
// Filter out duplicate tables without schema
relatedTables = filterDuplicateTables(relatedTables);
return relatedTables.map((relTable) => {
const relSchema = relTable.schema || view.schema; // Use view's schema if relSchema is undefined
const relTableName = relTable.tableName;
const table = tables.find(
(table) =>
table.name === relTableName &&
(table.schema || '') === relSchema
);
if (table) {
const dependency: DBDependency = {
id: generateId(),
schema: view.schema,
tableId: table.id, // related table
dependentSchema: table.schema,
dependentTableId: viewTable.id, // dependent view
createdAt: Date.now(),
};
return dependency;
} else {
console.warn(
`Dependent table ${relSchema}.${relTableName} not found for view ${view.schema}.${view.view_name}`
);
return null;
}
});
} catch (error) {
console.error(
`Error parsing view ${view.schema}.${view.view_name}:`,
error
);
return [];
}
} else {
console.warn(
`View definition missing for ${view.schema}.${view.view_name}`
);
return [];
}
})
.filter((dependency) => dependency !== null);
return dependencies;
};
// Add this new function to filter out duplicate tables
function filterDuplicateTables(
tables: { schema?: string; tableName: string }[]
): { schema?: string; tableName: string }[] {
const tableMap = new Map<string, { schema?: string; tableName: string }>();
for (const table of tables) {
const key = table.tableName;
const existingTable = tableMap.get(key);
if (!existingTable || (table.schema && !existingTable.schema)) {
tableMap.set(key, table);
}
}
return Array.from(tableMap.values());
}
// Preprocess the view_definition to remove schema from CREATE VIEW
function preprocessViewDefinition(viewDefinition: string): string {
if (!viewDefinition) {
return '';
}
// Remove leading and trailing whitespace
viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
// Replace escaped double quotes with regular ones
viewDefinition = viewDefinition.replace(/\\"/g, '"');
// Replace 'CREATE MATERIALIZED VIEW' with 'CREATE VIEW'
viewDefinition = viewDefinition.replace(
/CREATE\s+MATERIALIZED\s+VIEW/i,
'CREATE VIEW'
);
// Regular expression to match 'CREATE VIEW [schema.]view_name [ (column definitions) ] AS'
// This regex captures the view name and skips any content between the view name and 'AS'
const regex =
/CREATE\s+VIEW\s+(?:(?:`[^`]+`|"[^"]+"|\w+)\.)?(?:`([^`]+)`|"([^"]+)"|(\w+))[\s\S]*?\bAS\b\s+/i;
const match = viewDefinition.match(regex);
let modifiedDefinition: string;
if (match) {
const viewName = match[1] || match[2] || match[3];
// Extract the SQL after the 'AS' keyword
const restOfDefinition = viewDefinition.substring(
match.index! + match[0].length
);
// Replace double-quoted identifiers with unquoted ones
let modifiedSQL = restOfDefinition.replace(/"(\w+)"/g, '$1');
// Replace '::' type casts with 'CAST' expressions
modifiedSQL = modifiedSQL.replace(
/\(([^()]+)\)::(\w+)/g,
'CAST($1 AS $2)'
);
// Remove ClickHouse-specific syntax that may still be present
// For example, remove SETTINGS clauses inside the SELECT statement
modifiedSQL = modifiedSQL.replace(/\bSETTINGS\b[\s\S]*$/i, '');
modifiedDefinition = `CREATE VIEW ${viewName} AS ${modifiedSQL}`;
} else {
console.warn('Could not preprocess view definition:', viewDefinition);
modifiedDefinition = viewDefinition;
}
return modifiedDefinition;
}
// Preprocess the view_definition for SQL Server
function preprocessViewDefinitionSQLServer(viewDefinition: string): string {
if (!viewDefinition) {
return '';
}
// Remove BOM if present
viewDefinition = viewDefinition.replace(/^\uFEFF/, '');
// Normalize whitespace
viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
// Remove square brackets and replace with double quotes
viewDefinition = viewDefinition.replace(/\[([^\]]+)\]/g, '"$1"');
// Remove database names from fully qualified identifiers
viewDefinition = viewDefinition.replace(
/"([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"/g,
'"$2"."$3"'
);
// Replace SQL Server functions with PostgreSQL equivalents
viewDefinition = viewDefinition.replace(/\bGETDATE\(\)/gi, 'NOW()');
viewDefinition = viewDefinition.replace(/\bISNULL\(/gi, 'COALESCE(');
// Replace 'TOP N' with 'LIMIT N' at the end of the query
const topMatch = viewDefinition.match(/SELECT\s+TOP\s+(\d+)/i);
if (topMatch) {
const topN = topMatch[1];
viewDefinition = viewDefinition.replace(
/SELECT\s+TOP\s+\d+/i,
'SELECT'
);
viewDefinition = viewDefinition.replace(/;+\s*$/, ''); // Remove semicolons at the end
viewDefinition += ` LIMIT ${topN}`;
}
viewDefinition = viewDefinition.replace(/\n/g, ''); // Remove newlines
// Adjust CREATE VIEW syntax
const regex =
/CREATE\s+VIEW\s+(?:"?([^".\s]+)"?\.)?"?([^".\s]+)"?\s+AS\s+/i;
const match = viewDefinition.match(regex);
let modifiedDefinition: string;
if (match) {
const viewName = match[2];
const modifiedSQL = viewDefinition.substring(
match.index! + match[0].length
);
// Remove semicolons at the end
const finalSQL = modifiedSQL.replace(/;+\s*$/, '');
modifiedDefinition = `CREATE VIEW "${viewName}" AS ${finalSQL}`;
} else {
console.warn('Could not preprocess view definition:', viewDefinition);
modifiedDefinition = viewDefinition;
}
return modifiedDefinition;
}
// Preprocess the view_definition to remove schema from CREATE VIEW
function preprocessViewDefinitionMySQL(viewDefinition: string): string {
if (!viewDefinition) {
return '';
}
// Remove any trailing semicolons
viewDefinition = viewDefinition.replace(/;\s*$/, '');
// Remove backticks from identifiers
viewDefinition = viewDefinition.replace(/`/g, '');
// Remove unnecessary parentheses around joins and ON clauses
viewDefinition = removeRedundantParentheses(viewDefinition);
return viewDefinition;
}
function removeRedundantParentheses(sql: string): string {
// Regular expressions to match unnecessary parentheses
const patterns = [
/\(\s*(JOIN\s+[^()]+?)\s*\)/gi,
/\(\s*(ON\s+[^()]+?)\s*\)/gi,
// Additional patterns if necessary
];
let prevSql;
do {
prevSql = sql;
patterns.forEach((pattern) => {
sql = sql.replace(pattern, '$1');
});
} while (sql !== prevSql);
return sql;
}
function extractTablesFromAST(
ast: AST | AST[]
): { schema?: string; tableName: string }[] {
const tablesMap = new Map<string, { schema: string; tableName: string }>();
const visitedNodes = new Set();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function traverse(node: any) {
if (!node || visitedNodes.has(node)) return;
visitedNodes.add(node);
if (Array.isArray(node)) {
node.forEach(traverse);
} else if (typeof node === 'object') {
// Check if node represents a table
if (
Object.hasOwnProperty.call(node, 'table') &&
typeof node.table === 'string'
) {
let schema = node.db || node.schema;
const tableName = node.table;
if (tableName) {
// Assign default schema if undefined
schema = schemaNameToDomainSchemaName(schema) || '';
const key = `${schema}.${tableName}`;
if (!tablesMap.has(key)) {
tablesMap.set(key, { schema, tableName });
}
}
}
// Recursively traverse all properties
for (const key in node) {
if (Object.hasOwnProperty.call(node, key)) {
traverse(node[key]);
}
}
}
}
traverse(ast);
return Array.from(tablesMap.values());
}

View File

@@ -0,0 +1,68 @@
import type { DBField } from '@/lib/domain';
import type { ColumnInfo } from '../metadata-types/column-info';
import type { AggregatedIndexInfo } from '../metadata-types/index-info';
import type { PrimaryKeyInfo } from '../metadata-types/primary-key-info';
import type { TableInfo } from '../metadata-types/table-info';
import { generateId } from '@/lib/utils';
export const createFieldsFromMetadata = ({
tableColumns,
tablePrimaryKeys,
aggregatedIndexes,
}: {
tableColumns: ColumnInfo[];
tableSchema?: string;
tableInfo: TableInfo;
tablePrimaryKeys: PrimaryKeyInfo[];
aggregatedIndexes: AggregatedIndexInfo[];
}) => {
const uniqueColumns = tableColumns.reduce((acc, col) => {
if (!acc.has(col.name)) {
acc.set(col.name, col);
}
return acc;
}, new Map<string, ColumnInfo>());
const sortedColumns = Array.from(uniqueColumns.values()).sort(
(a, b) => a.ordinal_position - b.ordinal_position
);
const tablePrimaryKeysColumns = tablePrimaryKeys.map((pk) =>
pk.column.trim()
);
return sortedColumns.map(
(col: ColumnInfo): DBField => ({
id: generateId(),
name: col.name,
type: {
id: col.type.split(' ').join('_').toLowerCase(),
name: col.type.toLowerCase(),
},
primaryKey: tablePrimaryKeysColumns.includes(col.name),
unique: Object.values(aggregatedIndexes).some(
(idx) =>
idx.unique &&
idx.columns.length === 1 &&
idx.columns[0].name === col.name
),
nullable: Boolean(col.nullable),
...(col.character_maximum_length &&
col.character_maximum_length !== 'null'
? { characterMaximumLength: col.character_maximum_length }
: {}),
...(col.precision?.precision
? { precision: col.precision.precision }
: {}),
...(col.precision?.scale ? { scale: col.precision.scale } : {}),
...(col.default ? { default: col.default } : {}),
...(col.collation ? { collation: col.collation } : {}),
...(col.is_identity !== undefined
? { increment: col.is_identity }
: {}),
...(col.is_array !== undefined ? { isArray: col.is_array } : {}),
createdAt: Date.now(),
comments: col.comment ? col.comment : undefined,
})
);
};

View File

@@ -0,0 +1,82 @@
import type { DatabaseEdition, Diagram } from '@/lib/domain';
import { adjustTablePositions, DatabaseType } from '@/lib/domain';
import { generateDiagramId } from '@/lib/utils';
import type { DatabaseMetadata } from '../metadata-types/database-metadata';
import { createCustomTypesFromMetadata } from './custom-types';
import { createRelationshipsFromMetadata } from './relationships';
import { createTablesFromMetadata } from './tables';
import { createDependenciesFromMetadata } from './dependencies';
export const loadFromDatabaseMetadata = async ({
databaseType,
databaseMetadata,
diagramNumber,
databaseEdition,
}: {
databaseType: DatabaseType;
databaseMetadata: DatabaseMetadata;
diagramNumber?: number;
databaseEdition?: DatabaseEdition;
}): Promise<Diagram> => {
const {
fk_info: foreignKeys,
views: views,
custom_types: customTypes,
} = databaseMetadata;
const tables = createTablesFromMetadata({
databaseMetadata,
databaseType,
});
const relationships = createRelationshipsFromMetadata({
foreignKeys,
tables,
});
const dependencies = await createDependenciesFromMetadata({
views,
tables,
databaseType,
});
const dbCustomTypes = customTypes
? createCustomTypesFromMetadata({
customTypes,
})
: [];
const adjustedTables = adjustTablePositions({
tables,
relationships,
mode: 'perSchema',
});
const sortedTables = adjustedTables.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;
});
const diagram: Diagram = {
id: generateDiagramId(),
name: databaseMetadata.database_name
? `${databaseMetadata.database_name}`
: diagramNumber
? `Diagram ${diagramNumber}`
: 'New Diagram',
databaseType: databaseType ?? DatabaseType.GENERIC,
databaseEdition,
tables: sortedTables,
relationships,
dependencies,
customTypes: dbCustomTypes,
createdAt: new Date(),
updatedAt: new Date(),
};
return diagram;
};

View File

@@ -0,0 +1,24 @@
import type { DBField, DBIndex, IndexType } from '@/lib/domain';
import type { AggregatedIndexInfo } from '../metadata-types/index-info';
import { generateId } from '@/lib/utils';
export const createIndexesFromMetadata = ({
aggregatedIndexes,
fields,
}: {
aggregatedIndexes: AggregatedIndexInfo[];
fields: DBField[];
}): DBIndex[] =>
aggregatedIndexes.map(
(idx): DBIndex => ({
id: generateId(),
name: idx.name,
unique: Boolean(idx.unique),
fieldIds: idx.columns
.sort((a, b) => a.position - b.position)
.map((c) => fields.find((f) => f.name === c.name)?.id)
.filter((id): id is string => id !== undefined),
createdAt: Date.now(),
type: idx.index_type?.toLowerCase() as IndexType,
})
);

View File

@@ -0,0 +1,85 @@
import type {
Cardinality,
DBField,
DBRelationship,
DBTable,
} from '@/lib/domain';
import { schemaNameToDomainSchemaName } from '@/lib/domain';
import type { ForeignKeyInfo } from '../metadata-types/foreign-key-info';
import { generateId } from '@/lib/utils';
const determineCardinality = (
field: DBField,
isTablePKComplex: boolean
): Cardinality => {
return field.unique || (field.primaryKey && !isTablePKComplex)
? 'one'
: 'many';
};
export const createRelationshipsFromMetadata = ({
foreignKeys,
tables,
}: {
foreignKeys: ForeignKeyInfo[];
tables: DBTable[];
}): DBRelationship[] => {
return foreignKeys
.map((fk: ForeignKeyInfo): DBRelationship | null => {
const schema = schemaNameToDomainSchemaName(fk.schema);
const sourceTable = tables.find(
(table) => table.name === fk.table && table.schema === schema
);
const targetSchema = schemaNameToDomainSchemaName(
fk.reference_schema
);
const targetTable = tables.find(
(table) =>
table.name === fk.reference_table &&
table.schema === targetSchema
);
const sourceField = sourceTable?.fields.find(
(field) => field.name === fk.column
);
const targetField = targetTable?.fields.find(
(field) => field.name === fk.reference_column
);
const isSourceTablePKComplex =
(sourceTable?.fields.filter((field) => field.primaryKey) ?? [])
.length > 1;
const isTargetTablePKComplex =
(targetTable?.fields.filter((field) => field.primaryKey) ?? [])
.length > 1;
if (sourceTable && targetTable && sourceField && targetField) {
const sourceCardinality = determineCardinality(
sourceField,
isSourceTablePKComplex
);
const targetCardinality = determineCardinality(
targetField,
isTargetTablePKComplex
);
return {
id: generateId(),
name: fk.foreign_key_name,
sourceSchema: schema,
targetSchema: targetSchema,
sourceTableId: sourceTable.id,
targetTableId: targetTable.id,
sourceFieldId: sourceField.id,
targetFieldId: targetField.id,
sourceCardinality,
targetCardinality,
createdAt: Date.now(),
};
}
return null;
})
.filter((rel) => rel !== null) as DBRelationship[];
};

View File

@@ -0,0 +1,228 @@
import type { DBIndex, DBTable } from '@/lib/domain';
import {
DatabaseType,
generateTableKey,
schemaNameToDomainSchemaName,
} from '@/lib/domain';
import type { DatabaseMetadata } from '../metadata-types/database-metadata';
import type { TableInfo } from '../metadata-types/table-info';
import { createAggregatedIndexes } from '../metadata-types/index-info';
import {
decodeBase64ToUtf16LE,
decodeBase64ToUtf8,
generateId,
} from '@/lib/utils';
import {
defaultTableColor,
materializedViewColor,
viewColor,
} from '@/lib/colors';
import { createFieldsFromMetadata } from './fields';
import { createIndexesFromMetadata } from './indexes';
export const decodeViewDefinition = (
databaseType: DatabaseType,
viewDefinition?: string
): string => {
if (!viewDefinition) {
return '';
}
let decodedViewDefinition: string;
if (databaseType === DatabaseType.SQL_SERVER) {
decodedViewDefinition = decodeBase64ToUtf16LE(viewDefinition);
} else {
decodedViewDefinition = decodeBase64ToUtf8(viewDefinition);
}
return decodedViewDefinition;
};
export const createTablesFromMetadata = ({
databaseMetadata,
databaseType,
}: {
databaseMetadata: DatabaseMetadata;
databaseType: DatabaseType;
}): DBTable[] => {
const {
tables: tableInfos,
pk_info: primaryKeys,
columns,
indexes,
views: views,
} = databaseMetadata;
// Pre-compute view names for faster lookup if there are views
const viewNamesSet = new Set<string>();
const materializedViewNamesSet = new Set<string>();
if (views && views.length > 0) {
views.forEach((view) => {
const key = generateTableKey({
schemaName: view.schema,
tableName: view.view_name,
});
viewNamesSet.add(key);
if (
view.view_definition &&
decodeViewDefinition(databaseType, view.view_definition)
.toLowerCase()
.includes('materialized')
) {
materializedViewNamesSet.add(key);
}
});
}
// Pre-compute lookup maps for better performance
const columnsByTable = new Map<string, (typeof columns)[0][]>();
const indexesByTable = new Map<string, (typeof indexes)[0][]>();
const primaryKeysByTable = new Map<string, (typeof primaryKeys)[0][]>();
// Group columns by table
columns.forEach((col) => {
const key = generateTableKey({
schemaName: col.schema,
tableName: col.table,
});
if (!columnsByTable.has(key)) {
columnsByTable.set(key, []);
}
columnsByTable.get(key)!.push(col);
});
// Group indexes by table
indexes.forEach((idx) => {
const key = generateTableKey({
schemaName: idx.schema,
tableName: idx.table,
});
if (!indexesByTable.has(key)) {
indexesByTable.set(key, []);
}
indexesByTable.get(key)!.push(idx);
});
// Group primary keys by table
primaryKeys.forEach((pk) => {
const key = generateTableKey({
schemaName: pk.schema,
tableName: pk.table,
});
if (!primaryKeysByTable.has(key)) {
primaryKeysByTable.set(key, []);
}
primaryKeysByTable.get(key)!.push(pk);
});
const result = tableInfos.map((tableInfo: TableInfo) => {
const tableSchema = schemaNameToDomainSchemaName(tableInfo.schema);
const tableKey = generateTableKey({
schemaName: tableInfo.schema,
tableName: tableInfo.table,
});
// Use pre-computed lookups instead of filtering entire arrays
const tableIndexes = indexesByTable.get(tableKey) || [];
const tablePrimaryKeys = primaryKeysByTable.get(tableKey) || [];
const tableColumns = columnsByTable.get(tableKey) || [];
// Aggregate indexes with multiple columns
const aggregatedIndexes = createAggregatedIndexes({
tableInfo,
tableSchema,
tableIndexes,
});
const fields = createFieldsFromMetadata({
aggregatedIndexes,
tableColumns,
tablePrimaryKeys,
tableInfo,
tableSchema,
});
// Check for composite primary key and find matching index name
const primaryKeyFields = fields.filter((f) => f.primaryKey);
let pkMatchingIndexName: string | undefined;
let pkIndex: DBIndex | undefined;
if (primaryKeyFields.length >= 1) {
// We have a composite primary key, look for an index that matches all PK columns
const pkFieldNames = primaryKeyFields.map((f) => f.name).sort();
// Find an index that matches the primary key columns exactly
const matchingIndex = aggregatedIndexes.find((index) => {
const indexColumnNames = index.columns
.map((c) => c.name)
.sort();
return (
indexColumnNames.length === pkFieldNames.length &&
indexColumnNames.every((col, i) => col === pkFieldNames[i])
);
});
if (matchingIndex) {
pkMatchingIndexName = matchingIndex.name;
// Create a special PK index
pkIndex = {
id: generateId(),
name: matchingIndex.name,
unique: true,
fieldIds: primaryKeyFields.map((f) => f.id),
createdAt: Date.now(),
isPrimaryKey: true,
};
}
}
// Filter out the index that matches the composite PK (to avoid duplication)
const filteredAggregatedIndexes = pkMatchingIndexName
? aggregatedIndexes.filter(
(idx) => idx.name !== pkMatchingIndexName
)
: aggregatedIndexes;
const dbIndexes = createIndexesFromMetadata({
aggregatedIndexes: filteredAggregatedIndexes,
fields,
});
// Add the PK index if it exists
if (pkIndex) {
dbIndexes.push(pkIndex);
}
// Determine if the current table is a view by checking against pre-computed sets
const viewKey = generateTableKey({
schemaName: tableSchema,
tableName: tableInfo.table,
});
const isView = viewNamesSet.has(viewKey);
const isMaterializedView = materializedViewNamesSet.has(viewKey);
// Initial random positions; these will be adjusted later
return {
id: generateId(),
name: tableInfo.table,
schema: tableSchema,
x: Math.random() * 1000, // Placeholder X
y: Math.random() * 800, // Placeholder Y
fields,
indexes: dbIndexes,
color: isMaterializedView
? materializedViewColor
: isView
? viewColor
: defaultTableColor,
isView: isView,
isMaterializedView: isMaterializedView,
createdAt: Date.now(),
comments: tableInfo.comment ? tableInfo.comment : undefined,
};
});
return result;
};

View File

@@ -15,6 +15,8 @@ export interface ColumnInfo {
default?: string | null; // Default value for the column, nullable default?: string | null; // Default value for the column, nullable
collation?: string | null; collation?: string | null;
comment?: string | null; comment?: string | null;
is_identity?: boolean | null; // Indicates if the column is auto-increment/identity
is_array?: boolean | null; // Indicates if the column is an array type
} }
export const ColumnInfoSchema: z.ZodType<ColumnInfo> = z.object({ export const ColumnInfoSchema: z.ZodType<ColumnInfo> = z.object({
@@ -35,4 +37,6 @@ export const ColumnInfoSchema: z.ZodType<ColumnInfo> = z.object({
default: z.string().nullable().optional(), default: z.string().nullable().optional(),
collation: z.string().nullable().optional(), collation: z.string().nullable().optional(),
comment: z.string().nullable().optional(), comment: z.string().nullable().optional(),
is_identity: z.boolean().nullable().optional(),
is_array: z.boolean().nullable().optional(),
}); });

View File

@@ -127,7 +127,13 @@ cols AS (
',"default":"', null, ',"default":"', null,
'","collation":"', COALESCE(cols.COLLATION_NAME::TEXT, ''), '","collation":"', COALESCE(cols.COLLATION_NAME::TEXT, ''),
'","comment":"', COALESCE(replace(replace(dsc.description::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''), '","comment":"', COALESCE(replace(replace(dsc.description::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
'"}')), ',') AS cols_metadata '","is_identity":', CASE
WHEN cols.is_identity = 'YES' THEN 'true'
WHEN cols.column_default IS NOT NULL AND cols.column_default LIKE 'nextval(%' THEN 'true'
WHEN cols.column_default LIKE 'unique_rowid()%' THEN 'true'
ELSE 'false'
END,
'}')), ',') AS cols_metadata
FROM information_schema.columns cols FROM information_schema.columns cols
LEFT JOIN pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_class c
ON c.relname = cols.table_name ON c.relname = cols.table_name

View File

@@ -69,7 +69,9 @@ SELECT CAST(CONCAT(
',"ordinal_position":', cols.ordinal_position, ',"ordinal_position":', cols.ordinal_position,
',"nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'), ',"nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
',"default":"', ${withExtras ? withDefault : withoutDefault}, ',"default":"', ${withExtras ? withDefault : withoutDefault},
'","collation":"', IFNULL(cols.collation_name, ''), '"}') '","collation":"', IFNULL(cols.collation_name, ''),
'","is_identity":', IF(cols.extra LIKE '%auto_increment%', 'true', 'false'),
'"}')
) FROM ( ) FROM (
SELECT cols.table_schema, SELECT cols.table_schema,
cols.table_name, cols.table_name,
@@ -81,7 +83,8 @@ SELECT CAST(CONCAT(
cols.ordinal_position, cols.ordinal_position,
cols.is_nullable, cols.is_nullable,
cols.column_default, cols.column_default,
cols.collation_name cols.collation_name,
cols.extra
FROM information_schema.columns cols FROM information_schema.columns cols
WHERE cols.table_schema = DATABASE() WHERE cols.table_schema = DATABASE()
) AS cols), ''), ) AS cols), ''),

View File

@@ -92,7 +92,9 @@ export const getMySQLQuery = (
',"ordinal_position":', cols.ordinal_position, ',"ordinal_position":', cols.ordinal_position,
',"nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'), ',"nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
',"default":"', ${withExtras ? withDefault : withoutDefault}, ',"default":"', ${withExtras ? withDefault : withoutDefault},
'","collation":"', IFNULL(cols.collation_name, ''), '"}' '","collation":"', IFNULL(cols.collation_name, ''),
'","is_identity":', IF(cols.extra LIKE '%auto_increment%', 'true', 'false'),
'}'
))))) )))))
), indexes as ( ), indexes as (
(SELECT (@indexes:=NULL), (SELECT (@indexes:=NULL),

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