Compare commits

..

1 Commits

Author SHA1 Message Date
johnnyfish
92b3feed02 fix(table-node): remove min-width on data types and add field name edit icon 2025-07-30 12:08:00 +03:00
269 changed files with 6840 additions and 112032 deletions

View File

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

2
.nvmrc
View File

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

View File

@@ -1,185 +1,5 @@
# 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)
### Features
* add floating "Show All" button when tables are out of view ([#787](https://github.com/chartdb/chartdb/issues/787)) ([bda150d](https://github.com/chartdb/chartdb/commit/bda150d4b6d6fb90beb423efba69349d21a037a5))
* add table selection for large database imports ([#776](https://github.com/chartdb/chartdb/issues/776)) ([0d9f57a](https://github.com/chartdb/chartdb/commit/0d9f57a9c969a67e350d6bf25e07c3a9ef5bba39))
* **canvas:** Add filter tables on canvas ([#774](https://github.com/chartdb/chartdb/issues/774)) ([dfbcf05](https://github.com/chartdb/chartdb/commit/dfbcf05b2f595f5b7b77dd61abf77e6e07acaf8f))
* **custom-types:** add highlight fields option for custom types ([#726](https://github.com/chartdb/chartdb/issues/726)) ([7e0483f](https://github.com/chartdb/chartdb/commit/7e0483f1a5512a6a737baf61caf7513e043f2e96))
* **datatypes:** Add decimal / numeric attribute support + organize field row ([#715](https://github.com/chartdb/chartdb/issues/715)) ([778f85d](https://github.com/chartdb/chartdb/commit/778f85d49214232a39710e47bb5d4ec41b75d427))
* **dbml:** Edit Diagram Directly from DBML ([#819](https://github.com/chartdb/chartdb/issues/819)) ([1b0390f](https://github.com/chartdb/chartdb/commit/1b0390f0b7652fe415540b7942cf53ec87143f08))
* **default value:** add default value option to table field settings ([#770](https://github.com/chartdb/chartdb/issues/770)) ([c9ea7da](https://github.com/chartdb/chartdb/commit/c9ea7da0923ff991cb936235674d9a52b8186137))
* enhance primary key and unique field handling logic ([#817](https://github.com/chartdb/chartdb/issues/817)) ([39247b7](https://github.com/chartdb/chartdb/commit/39247b77a299caa4f29ea434af3028155c6d37ed))
* implement area grouping with parent-child relationships ([#762](https://github.com/chartdb/chartdb/issues/762)) ([b35e175](https://github.com/chartdb/chartdb/commit/b35e17526b3c9b918928ae5f3f89711ea7b2529c))
* **schema:** support create new schema ([#801](https://github.com/chartdb/chartdb/issues/801)) ([867903c](https://github.com/chartdb/chartdb/commit/867903cd5f24d96ce1fe718dc9b562e2f2b75276))
### Bug Fixes
* add open and create diagram to side menu ([#757](https://github.com/chartdb/chartdb/issues/757)) ([67f5ac3](https://github.com/chartdb/chartdb/commit/67f5ac303ebf5ada97d5c80fb08a2815ca205a91))
* add PostgreSQL tests and fix parsing SQL ([#760](https://github.com/chartdb/chartdb/issues/760)) ([5d33740](https://github.com/chartdb/chartdb/commit/5d337409d64d1078b538350016982a98e684c06c))
* area resizers size ([#830](https://github.com/chartdb/chartdb/issues/830)) ([23e93bf](https://github.com/chartdb/chartdb/commit/23e93bfd01d741dd3d11aa5c479cef97e1a86fa6))
* **area:** redo/undo after dragging an area with tables ([#767](https://github.com/chartdb/chartdb/issues/767)) ([6af94af](https://github.com/chartdb/chartdb/commit/6af94afc56cf8987b8fc9e3f0a9bfa966de35408))
* **canvas filter:** improve scroller on canvas filter ([#799](https://github.com/chartdb/chartdb/issues/799)) ([6bea827](https://github.com/chartdb/chartdb/commit/6bea82729362a8c7b73dc089ddd9e52bae176aa2))
* **canvas:** fix filter eye button ([#780](https://github.com/chartdb/chartdb/issues/780)) ([b7dbe54](https://github.com/chartdb/chartdb/commit/b7dbe54c83c75cfe3c556f7a162055dcfe2de23d))
* clone of custom types ([#804](https://github.com/chartdb/chartdb/issues/804)) ([b30162d](https://github.com/chartdb/chartdb/commit/b30162d98bc659a61aae023cdeaead4ce25c7ae9))
* **cockroachdb:** support schema creation for cockroachdb ([#803](https://github.com/chartdb/chartdb/issues/803)) ([dba372d](https://github.com/chartdb/chartdb/commit/dba372d25a8c642baf8600d05aa154882729d446))
* **dbml actions:** set dbml tooltips side ([#798](https://github.com/chartdb/chartdb/issues/798)) ([a119854](https://github.com/chartdb/chartdb/commit/a119854da7c935eb595984ea9398e04136ce60c4))
* **dbml editor:** move tooltips button to be on the right ([#797](https://github.com/chartdb/chartdb/issues/797)) ([bfbfd7b](https://github.com/chartdb/chartdb/commit/bfbfd7b843f96c894b1966ad95393b866c927466))
* **dbml export:** fix handle tables with same name under different schemas ([#807](https://github.com/chartdb/chartdb/issues/807)) ([18e9142](https://github.com/chartdb/chartdb/commit/18e914242faccd6376fe5a7cd5a4478667f065ee))
* **dbml export:** handle tables with same name under different schemas ([#806](https://github.com/chartdb/chartdb/issues/806)) ([e68837a](https://github.com/chartdb/chartdb/commit/e68837a34aa635fb6fc02c7f1289495e5c448242))
* **dbml field comments:** support export field comments in dbml ([#796](https://github.com/chartdb/chartdb/issues/796)) ([0ca7008](https://github.com/chartdb/chartdb/commit/0ca700873577bbfbf1dd3f8088c258fc89b10c53))
* **dbml import:** fix dbml import types + schemas ([#808](https://github.com/chartdb/chartdb/issues/808)) ([00bd535](https://github.com/chartdb/chartdb/commit/00bd535b3c62d26d25a6276d52beb10e26afad76))
* **dbml-export:** merge field attributes into single brackets and fix schema syntax ([#790](https://github.com/chartdb/chartdb/issues/790)) ([309ee9c](https://github.com/chartdb/chartdb/commit/309ee9cb0ff1f5a68ed183e3919e1a11a8410909))
* **dbml-import:** handle unsupported DBML features and add comprehensive tests ([#766](https://github.com/chartdb/chartdb/issues/766)) ([22d46e1](https://github.com/chartdb/chartdb/commit/22d46e1e90729730cc25dd6961bfe8c3d2ae0c98))
* **dbml:** dbml indentation ([#829](https://github.com/chartdb/chartdb/issues/829)) ([16f9f46](https://github.com/chartdb/chartdb/commit/16f9f4671e011eb66ba9594bed47570eda3eed66))
* **dbml:** dbml note syntax ([#826](https://github.com/chartdb/chartdb/issues/826)) ([337f7cd](https://github.com/chartdb/chartdb/commit/337f7cdab4759d15cb4d25a8c0e9394e99ba33d4))
* **dbml:** fix dbml output format ([#815](https://github.com/chartdb/chartdb/issues/815)) ([eed104b](https://github.com/chartdb/chartdb/commit/eed104be5ba2b7d9940ffac38e7877722ad764fc))
* **dbml:** fix schemas with same table names ([#828](https://github.com/chartdb/chartdb/issues/828)) ([0c300e5](https://github.com/chartdb/chartdb/commit/0c300e5e72cc5ff22cac42f8dbaed167061157c6))
* **dbml:** import dbml notes (table + fields) ([#827](https://github.com/chartdb/chartdb/issues/827)) ([b9a1e78](https://github.com/chartdb/chartdb/commit/b9a1e78b53c932c0b1a12ee38b62494a5c2f9348))
* **dbml:** support multiple relationships on same field in inline DBML ([#822](https://github.com/chartdb/chartdb/issues/822)) ([a5f8e56](https://github.com/chartdb/chartdb/commit/a5f8e56b3ca97b851b6953481644d3a3ff7ce882))
* **dbml:** support spaces in names ([#794](https://github.com/chartdb/chartdb/issues/794)) ([8f27f10](https://github.com/chartdb/chartdb/commit/8f27f10dec96af400dc2c12a30b22b3a346803a9))
* fix hotkeys on form elements ([#778](https://github.com/chartdb/chartdb/issues/778)) ([43d1dff](https://github.com/chartdb/chartdb/commit/43d1dfff71f2b960358a79b0112b78d11df91fb7))
* fix screen freeze after schema select ([#800](https://github.com/chartdb/chartdb/issues/800)) ([8aeb1df](https://github.com/chartdb/chartdb/commit/8aeb1df0ad353c49e91243453f24bfa5921a89ab))
* **i18n:** add Croatian (hr) language support ([#802](https://github.com/chartdb/chartdb/issues/802)) ([2eb48e7](https://github.com/chartdb/chartdb/commit/2eb48e75d303d622f51327d22502a6f78e7fb32d))
* improve SQL export formatting and add schema-aware FK grouping ([#783](https://github.com/chartdb/chartdb/issues/783)) ([6df588f](https://github.com/chartdb/chartdb/commit/6df588f40e6e7066da6125413b94466429d48767))
* lost in canvas button animation ([#793](https://github.com/chartdb/chartdb/issues/793)) ([a93ec2c](https://github.com/chartdb/chartdb/commit/a93ec2cab906d0e4431d8d1668adcf2dbfc3c80f))
* **readonly:** fix zoom out on readonly ([#818](https://github.com/chartdb/chartdb/issues/818)) ([8ffde62](https://github.com/chartdb/chartdb/commit/8ffde62c1a00893c4bf6b4dd39068df530375416))
* remove error lag after autofix ([#764](https://github.com/chartdb/chartdb/issues/764)) ([bf32c08](https://github.com/chartdb/chartdb/commit/bf32c08d37c02ee6d7946a41633bb97b2271fcb7))
* remove unnecessary import ([#791](https://github.com/chartdb/chartdb/issues/791)) ([87836e5](https://github.com/chartdb/chartdb/commit/87836e53d145b825f9c4f80abca72f418df50e6c))
* **scroll:** disable scroll x behavior ([#795](https://github.com/chartdb/chartdb/issues/795)) ([4bc71c5](https://github.com/chartdb/chartdb/commit/4bc71c52ff5c462800d8530b72a5aadb7d7f85ed))
* set focus on filter search ([#775](https://github.com/chartdb/chartdb/issues/775)) ([9949a46](https://github.com/chartdb/chartdb/commit/9949a46ee3ba7f46a2ea7f2c0d7101cc9336df4f))
* solve issue with multiple render of tables ([#823](https://github.com/chartdb/chartdb/issues/823)) ([0c7eaa2](https://github.com/chartdb/chartdb/commit/0c7eaa2df20cfb6994b7e6251c760a2d4581c879))
* **sql-export:** escape newlines and quotes in multi-line comments ([#765](https://github.com/chartdb/chartdb/issues/765)) ([f7f9290](https://github.com/chartdb/chartdb/commit/f7f92903def84a94ac0c66f625f96a6681383945))
* **sql-server:** improvment for sql-server import via sql script ([#789](https://github.com/chartdb/chartdb/issues/789)) ([79b8855](https://github.com/chartdb/chartdb/commit/79b885502e3385e996a52093a3ccd5f6e469993a))
* **table-node:** fix comment icon on field ([#786](https://github.com/chartdb/chartdb/issues/786)) ([745bdee](https://github.com/chartdb/chartdb/commit/745bdee86d07f1e9c3a2d24237c48c25b9a8eeea))
* **table-node:** improve field spacing ([#785](https://github.com/chartdb/chartdb/issues/785)) ([08eb9cc](https://github.com/chartdb/chartdb/commit/08eb9cc55f0077f53afea6f9ce720341e1a583c2))
* **table-select:** add loading indication for import ([#782](https://github.com/chartdb/chartdb/issues/782)) ([b46ed58](https://github.com/chartdb/chartdb/commit/b46ed58dff1ec74579fb1544dba46b0f77730c52))
* **ui:** reduce spacing between primary key icon and short field types ([#816](https://github.com/chartdb/chartdb/issues/816)) ([984b2ae](https://github.com/chartdb/chartdb/commit/984b2aeee22c43cb9bda77df2c22087973079af4))
* update MariaDB database import smart query ([#792](https://github.com/chartdb/chartdb/issues/792)) ([386e40a](https://github.com/chartdb/chartdb/commit/386e40a0bf93d9aef1486bb1e729d8f485e675eb))
* update multiple schemas toast to require user action ([#771](https://github.com/chartdb/chartdb/issues/771)) ([f56fab9](https://github.com/chartdb/chartdb/commit/f56fab9876fb9fc46c6c708231324a90d8a7851d))
* update relationship when table width changes via expand/shrink ([#825](https://github.com/chartdb/chartdb/issues/825)) ([bc52933](https://github.com/chartdb/chartdb/commit/bc52933b58bfe6bc73779d9401128254cbf497d5))
## [1.13.2](https://github.com/chartdb/chartdb/compare/v1.13.1...v1.13.2) (2025-07-06) ## [1.13.2](https://github.com/chartdb/chartdb/compare/v1.13.1...v1.13.2) (2025-07-06)

View File

@@ -4,9 +4,8 @@
<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="noindex, max-image-preview:large" /> <meta name="robots" content="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
@@ -16,19 +15,14 @@
<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 = const disableAnalytics = (window.env && window.env.DISABLE_ANALYTICS === 'true') ||
(window.env && window.env.DISABLE_ANALYTICS === 'true') || (typeof process !== 'undefined' && process.env && process.env.VITE_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);
} }

881
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.17.0", "version": "1.13.2",
"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.13.9", "@dbml/core": "^3.9.5",
"@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,24 +26,24 @@
"@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.14", "@radix-ui/react-dialog": "^1.1.6",
"@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.2", "@radix-ui/react-icons": "^1.3.0",
"@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.7", "@radix-ui/react-separator": "^1.1.2",
"@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.2.7", "@radix-ui/react-tooltip": "^1.1.8",
"@uidotdev/usehooks": "^2.4.1", "@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.8.2", "@xyflow/react": "^12.3.1",
"ahooks": "^3.8.1", "ahooks": "^3.8.1",
"ai": "^3.3.14", "ai": "^3.3.14",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",

View File

@@ -1,4 +1,4 @@
User-agent: * User-agent: *
Disallow: / Allow: /
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 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', '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',
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -1,137 +0,0 @@
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

@@ -31,21 +31,19 @@ export interface CodeSnippetAction {
label: string; label: string;
icon: LucideIcon; icon: LucideIcon;
onClick: () => void; onClick: () => void;
className?: string;
} }
export interface CodeSnippetProps { export interface CodeSnippetProps {
className?: string; className?: string;
code: string; code: string;
codeToCopy?: string; codeToCopy?: string;
language?: 'sql' | 'shell' | 'dbml'; language?: 'sql' | 'shell';
loading?: boolean; loading?: boolean;
autoScroll?: boolean; autoScroll?: boolean;
isComplete?: boolean; isComplete?: boolean;
editorProps?: React.ComponentProps<EditorType>; editorProps?: React.ComponentProps<EditorType>;
actions?: CodeSnippetAction[]; actions?: CodeSnippetAction[];
actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left'; actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left';
allowCopy?: boolean;
} }
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo( export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
@@ -60,7 +58,6 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
editorProps, editorProps,
actions, actions,
actionsTooltipSide, actionsTooltipSide,
allowCopy = true,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const monaco = useMonaco(); const monaco = useMonaco();
@@ -134,37 +131,33 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
{isComplete ? ( {isComplete ? (
<div className="absolute right-1 top-1 z-10 flex flex-col gap-1"> <div className="absolute right-1 top-1 z-10 flex flex-col gap-1">
{allowCopy ? ( <Tooltip
<Tooltip onOpenChange={setTooltipOpen}
onOpenChange={setTooltipOpen} open={isCopied || tooltipOpen}
open={isCopied || tooltipOpen} >
> <TooltipTrigger asChild>
<TooltipTrigger asChild> <span>
<span> <Button
<Button className="h-fit p-1.5"
className="h-fit p-1.5" variant="outline"
variant="outline" onClick={copyToClipboard}
onClick={copyToClipboard} >
> {isCopied ? (
{isCopied ? ( <CopyCheck size={16} />
<CopyCheck size={16} /> ) : (
) : ( <Copy size={16} />
<Copy size={16} /> )}
)} </Button>
</Button> </span>
</span> </TooltipTrigger>
</TooltipTrigger> <TooltipContent side={actionsTooltipSide}>
<TooltipContent {t(
side={actionsTooltipSide} isCopied
> ? 'copied'
{t( : 'copy_to_clipboard'
isCopied )}
? 'copied' </TooltipContent>
: 'copy_to_clipboard' </Tooltip>
)}
</TooltipContent>
</Tooltip>
) : null}
{actions && {actions &&
actions.length > 0 && actions.length > 0 &&
@@ -173,10 +166,7 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span> <span>
<Button <Button
className={cn( className="h-fit p-1.5"
'h-fit p-1.5',
action.className
)}
variant="outline" variant="outline"
onClick={action.onClick} onClick={action.onClick}
> >

View File

@@ -1,51 +0,0 @@
import type { DBMLError } from '@/lib/dbml/dbml-import/dbml-import-error';
import * as monaco from 'monaco-editor';
export const highlightErrorLine = ({
error,
model,
editorDecorationsCollection,
}: {
error: DBMLError;
model?: monaco.editor.ITextModel | null;
editorDecorationsCollection:
| monaco.editor.IEditorDecorationsCollection
| undefined;
}) => {
if (!model) return;
if (!editorDecorationsCollection) return;
const decorations = [
{
range: new monaco.Range(
error.line,
1,
error.line,
model.getLineMaxColumn(error.line)
),
options: {
isWholeLine: true,
className: 'dbml-error-line',
glyphMarginClassName: 'dbml-error-glyph',
hoverMessage: { value: error.message },
overviewRuler: {
color: '#ff0000',
position: monaco.editor.OverviewRulerLane.Right,
darkColor: '#ff0000',
},
},
},
];
editorDecorationsCollection?.set(decorations);
};
export const clearErrorHighlight = (
editorDecorationsCollection:
| monaco.editor.IEditorDecorationsCollection
| undefined
) => {
if (editorDecorationsCollection) {
editorDecorationsCollection.clear();
}
};

View File

@@ -9,14 +9,12 @@ 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: 'type', foreground: '4EC9B0' }, // Data types { token: 'datatype', foreground: '4EC9B0' }, // Data types
{ token: 'identifier', foreground: '9CDCFE' }, // Field names
], ],
colors: {}, colors: {},
}); });
@@ -25,14 +23,12 @@ 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: {},
}); });
@@ -41,59 +37,20 @@ 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', 'enum'], keywords: ['Table', 'Ref', 'Indexes'],
datatypes: dataTypesNames, datatypes: dataTypesNames,
operators: ['>', '<', '-'],
tokenizer: { tokenizer: {
root: [ root: [
// Comments [/\b(Table|Ref|Indexes)\b/, 'keyword'],
[/\/\/.*$/, '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/,
'keyword',
],
// Annotations in brackets
[/\[.*?\]/, 'annotation'], [/\[.*?\]/, 'annotation'],
// Strings
[/'''/, 'string', '@tripleQuoteString'], [/'''/, 'string', '@tripleQuoteString'],
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-terminated string [/".*?"/, 'string'],
[/'([^'\\]|\\.)*$/, 'string.invalid'], // non-terminated string [/'.*?'/, 'string'],
[/"/, 'string', '@string_double'],
[/'/, 'string', '@string_single'],
[/`.*?`/, 'string'], [/`.*?`/, 'string'],
[/[{}]/, 'delimiter'],
// Delimiters and operators [/[<>]/, 'operator'],
[/[{}()]/, 'delimiter'], [new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching
[/[<>-]/, '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,45 +5,27 @@ 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, disabled, popoverOnMouseDown, popoverOnClick }, ref) => { >(({ color, onChange }, ref) => {
return ( return (
<Popover> <Popover>
<PopoverTrigger <PopoverTrigger asChild ref={ref}>
asChild
ref={ref}
disabled={disabled}
{...(disabled ? { onClick: (e) => e.preventDefault() } : {})}
>
<div <div
className={cn( className="h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
'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 <PopoverContent className="w-fit">
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,7 +4,6 @@ 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;
@@ -33,75 +32,28 @@ 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 />
className, <DialogPrimitive.Content
children, ref={ref}
showClose, className={cn(
showBack, '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',
onBackClick, className
backButtonClassName, )}
blurBackground, {...props}
forceOverlay, >
...props {children}
}, {showClose && (
ref <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" />
<DialogPortal> <span className="sr-only">Close</span>
{forceOverlay ? ( </DialogPrimitive.Close>
<div )}
className={cn( </DialogPrimitive.Content>
'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', </DialogPortal>
{ ));
'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,16 +1,9 @@
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;
@@ -45,29 +38,26 @@ export const EmptyState = forwardRef<
className className
)} )}
> >
<Empty> <img
<EmptyHeader> src={
<EmptyMedia variant="icon"> effectiveTheme === 'dark'
{/* <Group /> */} ? EmptyStateImageDark
<img : EmptyStateImage
src={ }
effectiveTheme === 'dark' alt="Empty state"
? EmptyStateImageDark className={cn('mb-2 w-20', imageClassName)}
: EmptyStateImage />
} <Label className={cn('text-base', titleClassName)}>
alt="Empty state" {title}
className={cn('p-2', imageClassName)} </Label>
/> <Label
</EmptyMedia> className={cn(
<EmptyTitle className={titleClassName}> 'text-sm text-center font-normal text-muted-foreground',
{title} descriptionClassName
</EmptyTitle> )}
<EmptyDescription className={descriptionClassName}> >
{description} {description}
</EmptyDescription> </Label>
</EmptyHeader>
<EmptyContent />
</Empty>
</div> </div>
); );
} }

View File

@@ -1,105 +0,0 @@
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,13 +2,16 @@ import React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>( export interface InputProps
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-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', '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',
className className
)} )}
ref={ref} ref={ref}

View File

@@ -27,7 +27,6 @@ export interface SelectBoxOption {
regex?: string; regex?: string;
extractRegex?: RegExp; extractRegex?: RegExp;
group?: string; group?: string;
icon?: React.ReactNode;
} }
export interface SelectBoxProps { export interface SelectBoxProps {
@@ -54,11 +53,6 @@ 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>(
@@ -84,11 +78,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
open, open,
onOpenChange: setOpen, onOpenChange: setOpen,
popoverClassName, popoverClassName,
readonly,
footerButtons,
commandOnMouseDown,
commandOnClick,
onSearchChange,
}, },
ref ref
) => { ) => {
@@ -105,10 +94,6 @@ 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]
@@ -163,20 +148,18 @@ 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>
{!readonly ? ( <span
<span onClick={(e) => {
onClick={(e) => { e.preventDefault();
e.preventDefault(); handleSelect(option.value);
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 />
<Cross2Icon /> </span>
</span>
) : null}
</span> </span>
)), )),
[options, value, handleSelect, oneLine, keepOrder, readonly] [options, value, handleSelect, oneLine, keepOrder]
); );
const isAllSelected = React.useMemo( const isAllSelected = React.useMemo(
@@ -242,7 +225,6 @@ 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(
@@ -250,8 +232,6 @@ 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
@@ -266,11 +246,6 @@ 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
@@ -297,15 +272,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</CommandItem> </CommandItem>
); );
}, },
[ [value, multiple, searchTerm, handleSelect, optionSuffix]
value,
multiple,
searchTerm,
handleSelect,
optionSuffix,
commandOnClick,
commandOnMouseDown,
]
); );
return ( return (
@@ -313,7 +280,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' : ''} ${readonly ? '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' : ''}`,
className className
)} )}
> >
@@ -383,8 +350,6 @@ 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) => {
@@ -407,10 +372,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
<div className="relative"> <div className="relative">
<CommandInput <CommandInput
value={searchTerm} value={searchTerm}
onValueChange={(e) => { onValueChange={(e) => setSearchTerm(e)}
setSearchTerm(e);
onSearchChange?.(e);
}}
ref={ref} ref={ref}
placeholder={inputPlaceholder ?? 'Search...'} placeholder={inputPlaceholder ?? 'Search...'}
className="h-9" className="h-9"
@@ -477,9 +439,6 @@ 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,7 +29,6 @@ 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 = {
@@ -143,8 +142,6 @@ 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
} }
@@ -169,7 +166,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' | 'icon-extended' | 'none'; collapsible?: 'offcanvas' | 'icon' | 'none';
} }
>( >(
( (
@@ -248,8 +245,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-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4))]' ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended]' : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
)} )}
/> />
<div <div
@@ -260,8 +257,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)] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4)_+2px)]' ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
: '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', : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
className className
)} )}
{...props} {...props}
@@ -424,7 +421,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 group-data-[collapsible=icon-extended]:overflow-hidden', 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
className className
)} )}
{...props} {...props}
@@ -464,7 +461,6 @@ 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}
@@ -487,7 +483,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-extended]:hidden', 'group-data-[collapsible=icon]:hidden',
className className
)} )}
{...props} {...props}
@@ -536,7 +532,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-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', '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',
{ {
variants: { variants: {
variant: { variant: {
@@ -640,7 +636,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-extended]:hidden', 'group-data-[collapsible=icon]: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
@@ -757,7 +753,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-extended]:hidden', 'group-data-[collapsible=icon]:hidden',
className className
)} )}
{...props} {...props}

View File

@@ -13,17 +13,15 @@ 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 origin-[--radix-tooltip-content-transform-origin]', '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',
className className
)} )}
{...props} {...props}
/> />
// </TooltipPrimitive.Portal>
)); ));
TooltipContent.displayName = TooltipPrimitive.Content.displayName; TooltipContent.displayName = TooltipPrimitive.Content.displayName;

View File

@@ -42,7 +42,6 @@ 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<
@@ -63,14 +62,12 @@ 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
@@ -148,7 +145,6 @@ export function TreeView<
renderHoverComponent={renderHoverComponent} renderHoverComponent={renderHoverComponent}
renderActionsComponent={renderActionsComponent} renderActionsComponent={renderActionsComponent}
loadingNodeIds={loadingNodeIds} loadingNodeIds={loadingNodeIds}
disableCache={disableCache}
/> />
))} ))}
</div> </div>
@@ -183,7 +179,6 @@ 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>>({
@@ -206,16 +201,11 @@ 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];
// If cache is disabled, always use fresh node.children const children = loadedChildren[node.id] || 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 =
@@ -433,7 +423,6 @@ 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,12 +28,10 @@ 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>({});
@@ -91,8 +89,8 @@ export function useTree<
// Get any previously fetched children // Get any previously fetched children
const previouslyFetchedChildren = loadedChildren[nodeId] || []; const previouslyFetchedChildren = loadedChildren[nodeId] || [];
// Only cache if caching is enabled // If we have static children, merge them with any previously fetched children
if (!disableCache && staticChildren?.length) { if (staticChildren?.length) {
const mergedChildren = mergeChildren( const mergedChildren = mergeChildren(
staticChildren, staticChildren,
previouslyFetchedChildren previouslyFetchedChildren
@@ -112,8 +110,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 and cache is enabled // If we haven't loaded dynamic children yet
if (!disableCache && !previouslyFetchedChildren.length) { if (!previouslyFetchedChildren.length) {
setLoading((prev) => ({ ...prev, [nodeId]: true })); setLoading((prev) => ({ ...prev, [nodeId]: true }));
try { try {
const fetchedChildren = await fetchChildren?.( const fetchedChildren = await fetchChildren?.(
@@ -142,14 +140,7 @@ export function useTree<
} }
} }
}, },
[ [expanded, loadedChildren, fetchChildren, mergeChildren, setExpanded]
expanded,
loadedChildren,
fetchChildren,
mergeChildren,
setExpanded,
disableCache,
]
); );
return { return {

View File

@@ -2,24 +2,6 @@ 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;
@@ -32,42 +14,6 @@ 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>({
@@ -77,15 +23,4 @@ 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,74 +1,28 @@
import React, { import React, { type ReactNode, useCallback, useState } from '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 { adjustTablePositions } from '@/lib/domain/db-table'; import {
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 { const { tables, relationships, updateTablesState, filteredSchemas } =
tables, useChartDB();
relationships, const { fitView } = useReactFlow();
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 } = {
@@ -78,19 +32,9 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
const newTables = adjustTablePositions({ const newTables = adjustTablePositions({
relationships, relationships,
tables: tables.filter((table) => tables: tables.filter((table) =>
filterTable({ shouldShowTablesBySchemaFilter(table, filteredSchemas)
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[databaseType],
},
})
), ),
areas, mode: 'all', // Use 'all' mode for manual reordering
mode: 'all',
}); });
const updatedOverlapGraph = findOverlappingTables({ const updatedOverlapGraph = findOverlappingTables({
@@ -125,77 +69,9 @@ 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={{
@@ -205,17 +81,6 @@ 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,7 +12,6 @@ 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'
@@ -75,7 +74,6 @@ 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;
@@ -83,6 +81,9 @@ 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: (
@@ -94,10 +95,6 @@ export interface ChartDBContext {
updateDiagramUpdatedAt: () => Promise<void>; updateDiagramUpdatedAt: () => Promise<void>;
clearDiagramData: () => Promise<void>; clearDiagramData: () => Promise<void>;
deleteDiagram: () => Promise<void>; deleteDiagram: () => Promise<void>;
updateDiagramData: (
diagram: Diagram,
options?: { forceUpdateStorage?: boolean }
) => Promise<void>;
// Database type operations // Database type operations
updateDatabaseType: (databaseType: DatabaseType) => Promise<void>; updateDatabaseType: (databaseType: DatabaseType) => Promise<void>;
@@ -257,31 +254,6 @@ 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'>>
@@ -308,6 +280,11 @@ 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>({
@@ -319,9 +296,10 @@ 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: '',
@@ -339,7 +317,6 @@ export const chartDBContext = createContext<ChartDBContext>({
loadDiagramFromData: emptyFn, loadDiagramFromData: emptyFn,
clearDiagramData: emptyFn, clearDiagramData: emptyFn,
deleteDiagram: emptyFn, deleteDiagram: emptyFn,
updateDiagramData: emptyFn,
// Database type operations // Database type operations
updateDatabaseType: emptyFn, updateDatabaseType: emptyFn,
@@ -396,15 +373,6 @@ 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,
@@ -413,4 +381,9 @@ 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,15 +1,12 @@
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useEffect, 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 { defaultTableColor, defaultAreaColor, viewColor } from '@/lib/colors'; import { randomColor } 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 { import type { DBIndex } from '@/lib/domain/db-index';
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';
@@ -20,11 +17,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';
@@ -32,6 +29,7 @@ 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;
@@ -42,11 +40,16 @@ 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 storageDB = useStorage(); let db = useStorage();
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());
@@ -68,18 +71,17 @@ export const ChartDBProvider: React.FC<
const [customTypes, setCustomTypes] = useState<DBCustomType[]>( const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
diagram?.customTypes ?? [] diagram?.customTypes ?? []
); );
const [notes, setNotes] = useState<Note[]>(diagram?.notes ?? []); const [hiddenTableIds, setHiddenTableIds] = useState<string[]>([]);
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 { tablesToAdd, fieldsToAdd, relationshipsToAdd } = event.data; const { tablesAdded, fieldsAdded, relationshipsAdded } = event.data;
setTables((tables) => setTables((tables) =>
[...tables, ...(tablesToAdd ?? [])].map((table) => { [...tables, ...(tablesAdded ?? [])].map((table) => {
const fields = fieldsToAdd.get(table.id); const fields = fieldsAdded.get(table.id);
return fields return fields
? { ...table, fields: [...table.fields, ...fields] } ? { ...table, fields: [...table.fields, ...fields] }
: table; : table;
@@ -87,22 +89,31 @@ export const ChartDBProvider: React.FC<
); );
setRelationships((relationships) => [ setRelationships((relationships) => [
...relationships, ...relationships,
...(relationshipsToAdd ?? []), ...(relationshipsAdded ?? []),
]); ]);
}, []); }, []);
diffEvents.useSubscription(diffCalculatedHandler); diffEvents.useSubscription(diffCalculatedHandler);
const defaultSchemaName = useMemo( // Sync hiddenTableIds with config
() => defaultSchemas[databaseType], useEffect(() => {
[databaseType] if (diagramId) {
); 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)
@@ -113,11 +124,9 @@ export const ChartDBProvider: React.FC<
.filter((schema) => !!schema) as string[] .filter((schema) => !!schema) as string[]
), ),
] ]
.sort((a, b) => { .sort((a, b) =>
if (a === defaultSchemaName) return -1; a === defaultSchemaName ? -1 : a.localeCompare(b)
if (b === defaultSchemaName) return 1; )
return a.localeCompare(b);
})
.map( .map(
(schema): DBSchema => ({ (schema): DBSchema => ({
id: schemaNameToSchemaId(schema), id: schemaNameToSchemaId(schema),
@@ -131,11 +140,34 @@ export const ChartDBProvider: React.FC<
[tables, defaultSchemaName, databaseType] [tables, defaultSchemaName, databaseType]
); );
const db = useMemo( const filterSchemas: ChartDBContext['filterSchemas'] = useCallback(
() => (readonly ? storageInitialValue : storageDB), (schemaIds) => {
[storageDB, readonly] setSchemasFilter((prev) => ({
...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,
@@ -149,7 +181,6 @@ export const ChartDBProvider: React.FC<
dependencies, dependencies,
areas, areas,
customTypes, customTypes,
notes,
}), }),
[ [
diagramId, diagramId,
@@ -161,7 +192,6 @@ export const ChartDBProvider: React.FC<
dependencies, dependencies,
areas, areas,
customTypes, customTypes,
notes,
diagramCreatedAt, diagramCreatedAt,
diagramUpdatedAt, diagramUpdatedAt,
] ]
@@ -175,7 +205,6 @@ export const ChartDBProvider: React.FC<
setDependencies([]); setDependencies([]);
setAreas([]); setAreas([]);
setCustomTypes([]); setCustomTypes([]);
setNotes([]);
setDiagramUpdatedAt(updatedAt); setDiagramUpdatedAt(updatedAt);
resetRedoStack(); resetRedoStack();
@@ -188,7 +217,6 @@ 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]);
@@ -203,7 +231,6 @@ export const ChartDBProvider: React.FC<
setDependencies([]); setDependencies([]);
setAreas([]); setAreas([]);
setCustomTypes([]); setCustomTypes([]);
setNotes([]);
resetRedoStack(); resetRedoStack();
resetUndoStack(); resetUndoStack();
@@ -214,7 +241,6 @@ 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]);
@@ -353,18 +379,12 @@ export const ChartDBProvider: React.FC<
}, },
], ],
indexes: [], indexes: [],
color: attributes?.isView ? viewColor : defaultTableColor, color: randomColor(),
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;
@@ -656,30 +676,17 @@ 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) =>
if (table.id === tableId) { table.id === tableId
return updateTableFn(table); ? {
} ...table,
fields: table.fields.map((f) =>
return table; f.id === fieldId ? { ...f, ...field } : f
}) ),
}
: table
)
); );
const table = await db.getTable({ diagramId, id: tableId }); const table = await db.getTable({ diagramId, id: tableId });
@@ -694,7 +701,10 @@ export const ChartDBProvider: React.FC<
db.updateTable({ db.updateTable({
id: tableId, id: tableId,
attributes: { attributes: {
...updateTableFn(table), ...table,
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
}, },
}), }),
]); ]);
@@ -721,29 +731,19 @@ 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) =>
if (table.id === tableId) { table.id === tableId
return updateTableFn(table); ? {
} ...table,
fields: table.fields.filter(
return table; (f) => f.id !== fieldId
}) ),
}
: table
)
); );
events.emit({ events.emit({
@@ -767,7 +767,8 @@ export const ChartDBProvider: React.FC<
db.updateTable({ db.updateTable({
id: tableId, id: tableId,
attributes: { attributes: {
...updateTableFn(table), ...table,
fields: table.fields.filter((f) => f.id !== fieldId),
}, },
}), }),
]); ]);
@@ -1123,15 +1124,12 @@ 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: targetTableSchema, targetSchema: sourceTable?.schema,
targetTableId, targetTableId,
sourceFieldId, sourceFieldId,
targetFieldId, targetFieldId,
@@ -1453,7 +1451,7 @@ export const ChartDBProvider: React.FC<
y: 0, y: 0,
width: 300, width: 300,
height: 200, height: 200,
color: defaultAreaColor, color: randomColor(),
...attributes, ...attributes,
}; };
@@ -1536,130 +1534,6 @@ 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]
@@ -1686,7 +1560,6 @@ 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 } });
@@ -1707,31 +1580,19 @@ export const ChartDBProvider: React.FC<
setDiagramUpdatedAt, setDiagramUpdatedAt,
setHighlightedCustomTypeId, setHighlightedCustomTypeId,
events, events,
setNotes,
resetRedoStack, resetRedoStack,
resetUndoStack, resetUndoStack,
] ]
); );
const updateDiagramData: ChartDBContext['updateDiagramData'] = useCallback(
async (diagram, options) => {
const st = options?.forceUpdateStorage ? storageDB : db;
await st.deleteDiagram(diagram.id);
await st.addDiagram({ diagram });
loadDiagramFromData(diagram);
},
[db, storageDB, loadDiagramFromData]
);
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback( const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
async (diagramId: string) => { async (diagramId: string) => {
const diagram = await storageDB.getDiagram(diagramId, { const diagram = await db.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) {
@@ -1740,7 +1601,7 @@ export const ChartDBProvider: React.FC<
return diagram; return diagram;
}, },
[storageDB, loadDiagramFromData] [db, loadDiagramFromData]
); );
// Custom type operations // Custom type operations
@@ -1887,6 +1748,29 @@ 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={{
@@ -1897,12 +1781,12 @@ export const ChartDBProvider: React.FC<
relationships, relationships,
dependencies, dependencies,
areas, areas,
notes,
currentDiagram, currentDiagram,
schemas, schemas,
filteredSchemas,
events, events,
readonly, readonly,
updateDiagramData, filterSchemas,
updateDiagramId, updateDiagramId,
updateDiagramName, updateDiagramName,
loadDiagram, loadDiagram,
@@ -1959,15 +1843,11 @@ 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,9 +8,23 @@ 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, useState } from 'react'; import React, { useEffect } 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] = useState<ChartDBConfig | undefined>(); const [config, setConfig] = React.useState<ChartDBConfig | undefined>();
useEffect(() => { useEffect(() => {
const loadConfig = async () => { const loadConfig = async () => {
@@ -44,11 +44,84 @@ 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

@@ -1,50 +0,0 @@
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

@@ -1,559 +0,0 @@
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

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

View File

@@ -7,6 +7,7 @@ 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';
@@ -66,6 +67,12 @@ 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>({
@@ -89,4 +96,6 @@ export const dialogContext = createContext<DialogContext>({
closeExportDiagramDialog: emptyFn, closeExportDiagramDialog: emptyFn,
openImportDiagramDialog: emptyFn, openImportDiagramDialog: emptyFn,
closeImportDiagramDialog: emptyFn, closeImportDiagramDialog: emptyFn,
openImportDBMLDialog: emptyFn,
closeImportDBMLDialog: emptyFn,
}); });

View File

@@ -20,6 +20,8 @@ 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,
@@ -130,6 +132,11 @@ 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={{
@@ -158,6 +165,11 @@ 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}
@@ -192,6 +204,10 @@ 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 = {
tablesToAdd: DBTable[]; tablesAdded: DBTable[];
fieldsToAdd: Map<string, DBField[]>; fieldsAdded: Map<string, DBField[]>;
relationshipsToAdd: DBRelationship[]; relationshipsAdded: DBRelationship[];
}; };
export type DiffCalculatedEvent = DiffEventBase< export type DiffCalculatedEvent = DiffEventBase<
@@ -32,33 +32,21 @@ export interface DiffContext {
originalDiagram: Diagram | null; originalDiagram: Diagram | null;
diffMap: DiffMap; diffMap: DiffMap;
hasDiff: boolean; hasDiff: boolean;
isSummaryOnly: boolean;
calculateDiff: ({ calculateDiff: ({
diagram, diagram,
newDiagram, newDiagram,
options,
}: { }: {
diagram: Diagram; diagram: Diagram;
newDiagram: Diagram; newDiagram: Diagram;
options?: { }) => void;
summaryOnly?: boolean;
};
}) => { foundDiff: boolean };
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 }) => { getTableNewName: ({ tableId }: { tableId: string }) => string | null;
old: string; getTableNewColor: ({ tableId }: { tableId: string }) => string | null;
new: string;
} | null;
getTableNewColor: ({ tableId }: { tableId: string }) => {
old: string;
new: string;
} | null;
// field diff // field diff
checkIfFieldHasChange: ({ checkIfFieldHasChange: ({
@@ -70,46 +58,8 @@ 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: ({ getFieldNewName: ({ fieldId }: { fieldId: string }) => string | null;
fieldId, getFieldNewType: ({ fieldId }: { fieldId: string }) => DataType | 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: ({
fieldId,
}: {
fieldId: string;
}) => { old: string; new: string } | null;
getFieldNewScale: ({
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

@@ -32,11 +32,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const [fieldsChanged, setFieldsChanged] = React.useState< const [fieldsChanged, setFieldsChanged] = React.useState<
Map<string, boolean> Map<string, boolean>
>(new Map<string, boolean>()); >(new Map<string, boolean>());
const [isSummaryOnly, setIsSummaryOnly] = React.useState<boolean>(false);
const events = useEventEmitter<DiffEvent>(); const events = useEventEmitter<DiffEvent>();
const generateFieldsToAddMap = useCallback( const generateNewFieldsMap = useCallback(
({ ({
diffMap, diffMap,
newDiagram, newDiagram,
@@ -66,7 +65,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
[] []
); );
const findRelationshipsToAdd = useCallback( const findNewRelationships = useCallback(
({ ({
diffMap, diffMap,
newDiagram, newDiagram,
@@ -101,7 +100,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
diffMap: DiffMap; diffMap: DiffMap;
}): DiffCalculatedData => { }): DiffCalculatedData => {
return { return {
tablesToAdd: tablesAdded:
newDiagram?.tables?.filter((table) => { newDiagram?.tables?.filter((table) => {
const tableKey = getDiffMapKey({ const tableKey = getDiffMapKey({
diffObject: 'table', diffObject: 'table',
@@ -114,21 +113,21 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
); );
}) ?? [], }) ?? [],
fieldsToAdd: generateFieldsToAddMap({ fieldsAdded: generateNewFieldsMap({
diffMap: diffMap, diffMap: diffMap,
newDiagram: newDiagram, newDiagram: newDiagram,
}), }),
relationshipsToAdd: findRelationshipsToAdd({ relationshipsAdded: findNewRelationships({
diffMap: diffMap, diffMap: diffMap,
newDiagram: newDiagram, newDiagram: newDiagram,
}), }),
}; };
}, },
[findRelationshipsToAdd, generateFieldsToAddMap] [findNewRelationships, generateNewFieldsMap]
); );
const calculateDiff: DiffContext['calculateDiff'] = useCallback( const calculateDiff: DiffContext['calculateDiff'] = useCallback(
({ diagram, newDiagram: newDiagramArg, options }) => { ({ diagram, newDiagram: newDiagramArg }) => {
const { const {
diffMap: newDiffs, diffMap: newDiffs,
changedTables: newChangedTables, changedTables: newChangedTables,
@@ -140,7 +139,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
setFieldsChanged(newChangedFields); setFieldsChanged(newChangedFields);
setNewDiagram(newDiagramArg); setNewDiagram(newDiagramArg);
setOriginalDiagram(diagram); setOriginalDiagram(diagram);
setIsSummaryOnly(options?.summaryOnly ?? false);
events.emit({ events.emit({
action: 'diff_calculated', action: 'diff_calculated',
@@ -149,8 +147,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
newDiagram: newDiagramArg, newDiagram: newDiagramArg,
}), }),
}); });
return { foundDiff: !!newDiffs.size };
}, },
[setDiffMap, events, generateDiffCalculatedData] [setDiffMap, events, generateDiffCalculatedData]
); );
@@ -167,10 +163,7 @@ 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 { return diff.newValue as string;
new: diff.newValue as string,
old: diff.oldValue as string,
};
} }
} }
@@ -191,10 +184,7 @@ 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 { return diff.newValue as string;
new: diff.newValue as string,
old: diff.oldValue as string,
};
} }
} }
return null; return null;
@@ -285,10 +275,7 @@ 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 { return diff.newValue as string;
old: diff.oldValue as string,
new: diff.newValue as string,
};
} }
} }
@@ -309,160 +296,7 @@ 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 { return diff.newValue as DataType;
old: diff.oldValue as DataType,
new: diff.newValue as DataType,
};
}
}
return null;
},
[diffMap]
);
const getFieldNewPrimaryKey = useCallback<
DiffContext['getFieldNewPrimaryKey']
>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'primaryKey',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return {
old: diff.oldValue as boolean,
new: diff.newValue as boolean,
};
}
}
return null;
},
[diffMap]
);
const getFieldNewNullable = useCallback<DiffContext['getFieldNewNullable']>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'nullable',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return {
old: diff.oldValue as boolean,
new: diff.newValue as boolean,
};
}
}
return null;
},
[diffMap]
);
const getFieldNewCharacterMaximumLength = useCallback<
DiffContext['getFieldNewCharacterMaximumLength']
>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'characterMaximumLength',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return {
old: diff.oldValue as string,
new: diff.newValue as string,
};
}
}
return null;
},
[diffMap]
);
const getFieldNewScale = useCallback<DiffContext['getFieldNewScale']>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'scale',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return {
old: diff.oldValue as number,
new: diff.newValue as number,
};
}
}
return null;
},
[diffMap]
);
const getFieldNewPrecision = useCallback<
DiffContext['getFieldNewPrecision']
>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'precision',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
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,
};
} }
} }
@@ -505,15 +339,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
[diffMap] [diffMap]
); );
const resetDiff = useCallback<DiffContext['resetDiff']>(() => {
setDiffMap(new Map<string, ChartDBDiff>());
setTablesChanged(new Map<string, boolean>());
setFieldsChanged(new Map<string, boolean>());
setNewDiagram(null);
setOriginalDiagram(null);
setIsSummaryOnly(false);
}, []);
return ( return (
<diffContext.Provider <diffContext.Provider
value={{ value={{
@@ -521,10 +346,8 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
originalDiagram, originalDiagram,
diffMap, diffMap,
hasDiff: diffMap.size > 0, hasDiff: diffMap.size > 0,
isSummaryOnly,
calculateDiff, calculateDiff,
resetDiff,
// table diff // table diff
getTableNewName, getTableNewName,
@@ -539,12 +362,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
checkIfNewField, checkIfNewField,
getFieldNewName, getFieldNewName,
getFieldNewType, getFieldNewType,
getFieldNewPrimaryKey,
getFieldNewNullable,
getFieldNewCharacterMaximumLength,
getFieldNewScale,
getFieldNewPrecision,
getFieldNewIsArray,
// relationship diff // relationship diff
checkIfNewRelationship, checkIfNewRelationship,

View File

@@ -39,9 +39,6 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
addCustomTypes, addCustomTypes,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
addNotes,
removeNotes,
updateNote,
} = useChartDB(); } = useChartDB();
const redoActionHandlers = useMemo( const redoActionHandlers = useMemo(
@@ -138,15 +135,6 @@ 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,
@@ -172,9 +160,6 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
addCustomTypes, addCustomTypes,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
addNotes,
removeNotes,
updateNote,
] ]
); );
@@ -286,15 +271,6 @@ 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,
@@ -320,9 +296,6 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
addCustomTypes, addCustomTypes,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
addNotes,
removeNotes,
updateNote,
] ]
); );

View File

@@ -6,7 +6,6 @@ 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;
@@ -162,24 +161,6 @@ 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
@@ -203,10 +184,7 @@ 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,37 +2,29 @@ import { emptyFn } from '@/lib/utils';
import { createContext } from 'react'; import { createContext } from 'react';
export type SidebarSection = export type SidebarSection =
| 'dbml'
| 'tables' | 'tables'
| 'refs' | 'relationships'
| 'customTypes' | 'dependencies'
| 'visuals'; | 'areas'
| '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;
@@ -40,37 +32,32 @@ 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,
@@ -79,11 +66,12 @@ 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,9 +1,5 @@
import React from 'react'; import React from 'react';
import type { import type { LayoutContext, SidebarSection } from './layout-context';
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';
@@ -14,42 +10,34 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState< const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState<
string | undefined string | undefined
>(); >();
const [openedRefInSidebar, setOpenedRefInSidebar] = React.useState< const [openedRelationshipInSidebar, setOpenedRelationshipInSidebar] =
string | undefined React.useState<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'] =
() => setOpenedRefInSidebar(''); () => setOpenedRelationshipInSidebar('');
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] = const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
() => setOpenedRefInSidebar(''); () => setOpenedDependencyInSidebar('');
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('');
@@ -74,41 +62,25 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] = const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] =
(relationshipId) => { (relationshipId) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('refs'); setSelectedSidebarSection('relationships');
setOpenedRefInSidebar(relationshipId); setOpenedRelationshipInSidebar(relationshipId);
}; };
const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] = const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] =
(dependencyId) => { (dependencyId) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('refs'); setSelectedSidebarSection('dependencies');
setOpenedRefInSidebar(dependencyId); setOpenedDependencyInSidebar(dependencyId);
}; };
const openRefFromSidebar: LayoutContext['openRefFromSidebar'] = (refId) => {
showSidePanel();
setSelectedSidebarSection('refs');
setOpenedRefInSidebar(refId);
};
const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = ( const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = (
areaId areaId
) => { ) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('visuals'); setSelectedSidebarSection('areas');
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();
@@ -116,6 +88,11 @@ 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={{
@@ -123,6 +100,7 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
selectedSidebarSection, selectedSidebarSection,
openTableFromSidebar, openTableFromSidebar,
selectSidebarSection: setSelectedSidebarSection, selectSidebarSection: setSelectedSidebarSection,
openedRelationshipInSidebar,
openRelationshipFromSidebar, openRelationshipFromSidebar,
closeAllTablesInSidebar, closeAllTablesInSidebar,
closeAllRelationshipsInSidebar, closeAllRelationshipsInSidebar,
@@ -130,22 +108,18 @@ 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,6 +4,8 @@ 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;
@@ -11,8 +13,8 @@ export interface LocalConfigContext {
scrollAction: ScrollAction; scrollAction: ScrollAction;
setScrollAction: (action: ScrollAction) => void; setScrollAction: (action: ScrollAction) => void;
showDBViews: boolean; schemasFilter: SchemasFilter;
setShowDBViews: (showViews: boolean) => void; setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>;
showCardinality: boolean; showCardinality: boolean;
setShowCardinality: (showCardinality: boolean) => void; setShowCardinality: (showCardinality: boolean) => void;
@@ -20,12 +22,20 @@ 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;
} }
@@ -37,8 +47,8 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
scrollAction: 'pan', scrollAction: 'pan',
setScrollAction: emptyFn, setScrollAction: emptyFn,
showDBViews: false, schemasFilter: {},
setShowDBViews: emptyFn, setSchemasFilter: emptyFn,
showCardinality: true, showCardinality: true,
setShowCardinality: emptyFn, setShowCardinality: emptyFn,
@@ -46,12 +56,18 @@ 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,16 +1,18 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import type { ScrollAction } from './local-config-context'; import type { SchemasFilter, 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,
@@ -23,8 +25,10 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan' (localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
); );
const [showDBViews, setShowDBViews] = React.useState<boolean>( const [schemasFilter, setSchemasFilter] = React.useState<SchemasFilter>(
(localStorage.getItem(showDBViewsKey) || 'false') === 'true' JSON.parse(
localStorage.getItem(schemasFilterKey) || '{}'
) as SchemasFilter
); );
const [showCardinality, setShowCardinality] = React.useState<boolean>( const [showCardinality, setShowCardinality] = React.useState<boolean>(
@@ -36,6 +40,12 @@ 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'
); );
@@ -45,6 +55,12 @@ 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'
@@ -61,6 +77,13 @@ 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]);
@@ -70,13 +93,20 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
}, [scrollAction]); }, [scrollAction]);
useEffect(() => { useEffect(() => {
localStorage.setItem(showDBViewsKey, showDBViews.toString()); localStorage.setItem(schemasFilterKey, JSON.stringify(schemasFilter));
}, [showDBViews]); }, [schemasFilter]);
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,
@@ -91,16 +121,20 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
setTheme, setTheme,
scrollAction, scrollAction,
setScrollAction, setScrollAction,
showDBViews, schemasFilter,
setShowDBViews, setSchemasFilter,
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,22 +7,12 @@ 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?: {
@@ -31,7 +21,6 @@ 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,
@@ -41,7 +30,6 @@ 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: {
@@ -138,30 +126,12 @@ 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,
@@ -204,14 +174,6 @@ 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,8 +10,6 @@ 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,
@@ -42,18 +40,10 @@ 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:
@@ -200,44 +190,6 @@ 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);
@@ -265,34 +217,6 @@ 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({
@@ -572,56 +496,6 @@ 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 = [];
@@ -669,22 +543,9 @@ 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(
@@ -695,7 +556,6 @@ 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();
@@ -749,15 +609,6 @@ 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;
}, },
[ [
@@ -767,7 +618,6 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
listDependencies, listDependencies,
listRelationships, listRelationships,
listTables, listTables,
listNotes,
] ]
); );
@@ -780,7 +630,6 @@ 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);
@@ -809,10 +658,6 @@ 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;
}, },
[ [
@@ -822,7 +667,6 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
listDependencies, listDependencies,
listRelationships, listRelationships,
listTables, listTables,
listNotes,
] ]
); );
@@ -851,9 +695,6 @@ 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,
}),
]); ]);
} }
}, },
@@ -869,7 +710,6 @@ 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]
@@ -916,15 +756,6 @@ 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,14 +42,6 @@ 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
@@ -63,6 +55,49 @@ 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;
@@ -76,8 +111,8 @@ export interface ImportDatabaseProps {
>; >;
keepDialogAfterImport?: boolean; keepDialogAfterImport?: boolean;
title: string; title: string;
importMethod: ImportMethod; importMethod: 'query' | 'ddl';
setImportMethod: (method: ImportMethod) => void; setImportMethod: (method: 'query' | 'ddl') => void;
} }
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
@@ -97,7 +132,6 @@ 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();
@@ -112,20 +146,15 @@ 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 or dbml is valid // Check if the ddl is valid
useEffect(() => { useEffect(() => {
clearDecorations(); if (importMethod !== 'ddl') {
if (importMethod === 'query') {
setSqlValidation(null); setSqlValidation(null);
setShowAutoFixButton(false); setShowAutoFixButton(false);
return; return;
@@ -134,54 +163,9 @@ 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);
@@ -208,7 +192,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
setErrorMessage(result.error); setErrorMessage(result.error);
} }
}); });
}, [importMethod, scriptResult, databaseType, clearDecorations]); }, [importMethod, scriptResult, databaseType]);
// Check if the script result is a valid JSON // Check if the script result is a valid JSON
useEffect(() => { useEffect(() => {
@@ -336,8 +320,6 @@ 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) {
@@ -356,7 +338,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 = detectImportMethod(content); const detectedType = detectContentType(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);
@@ -370,7 +352,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
?.run(); ?.run();
}, 100); }, 100);
} }
// For DDL and DBML modes, do NOT format as it can break the syntax // For DDL mode, do NOT format as it can break the SQL
} 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) {
@@ -381,7 +363,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
?.run(); ?.run();
}, 100); }, 100);
} }
// For DDL and DBML modes or large files, do NOT format // For DDL mode or large files, do NOT format
} }
}); });
@@ -428,25 +410,16 @@ 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'
: importMethod === 'dbml' : 'SQL Script'}
? '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={ language={importMethod === 'query' ? 'json' : 'sql'}
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'
@@ -457,6 +430,7 @@ 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,
@@ -481,9 +455,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
</Suspense> </Suspense>
</div> </div>
{errorMessage || {errorMessage || (importMethod === 'ddl' && sqlValidation) ? (
((importMethod === 'ddl' || importMethod === 'dbml') &&
sqlValidation) ? (
<SQLValidationStatus <SQLValidationStatus
validation={sqlValidation} validation={sqlValidation}
errorMessage={errorMessage} errorMessage={errorMessage}

View File

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

View File

@@ -1,47 +0,0 @@
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".schema" > <output_file_path>`, code: `sqlite3 <database_file_path>\n.dump > <output_file_path>`,
example: `sqlite3 my_db.db\n".schema" > schema_export.sql`, example: `sqlite3 my_db.db\n.dump > 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-fit max-h-24"> <ScrollArea className="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-fit max-h-24"> <ScrollArea className="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/data/import-metadata/import'; import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
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,11 +22,6 @@ 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 {}
@@ -35,11 +30,11 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
}) => { }) => {
const { diagramId } = useChartDB(); const { diagramId } = useChartDB();
const { t } = useTranslation(); const { t } = useTranslation();
const [importMethod, setImportMethod] = useState<ImportMethod>('query'); const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
const [databaseType, setDatabaseType] = useState<DatabaseType>( const [databaseType, setDatabaseType] = useState<DatabaseType>(
DatabaseType.GENERIC DatabaseType.GENERIC
); );
const { closeCreateDiagramDialog } = useDialog(); const { closeCreateDiagramDialog, openImportDBMLDialog } = useDialog();
const { updateConfig } = useConfig(); const { updateConfig } = useConfig();
const [scriptResult, setScriptResult] = useState(''); const [scriptResult, setScriptResult] = useState('');
const [databaseEdition, setDatabaseEdition] = useState< const [databaseEdition, setDatabaseEdition] = useState<
@@ -94,14 +89,6 @@ 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;
@@ -165,6 +152,10 @@ 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,
@@ -173,13 +164,14 @@ 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' || importMethod === 'dbml') { if (importMethod === 'ddl') {
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,28 +1,20 @@
import React, { useCallback, useMemo, useState } from 'react'; import React, { 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: (selectedDatabaseType: DatabaseType) => void; onContinue: () => 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,
@@ -30,87 +22,69 @@ const TRANSACTIONAL_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(
() => () =>
currentDbTypes.slice( SUPPORTED_DB_TYPES.slice(
currentRow * ROW_SIZE, currentRow * ROW_SIZE,
currentRow * ROW_SIZE + TOTAL_SLOTS currentRow * ROW_SIZE + TOTAL_SLOTS
), ),
[currentRow, currentDbTypes] [currentRow]
); );
const hasNextRow = useMemo( const hasNextRow = useMemo(
() => (currentRow + 1) * ROW_SIZE < currentDbTypes.length, () => (currentRow + 1) * ROW_SIZE < SUPPORTED_DB_TYPES.length,
[currentRow, currentDbTypes] [currentRow]
); );
const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]); const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]);
const toggleRow = useCallback(() => { const toggleRow = () => {
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]); };
const handleTabChange = useCallback((value: string) => { return (
setActiveTab(value as 'transactional' | 'analytical'); <div className="flex flex-1 flex-col items-center justify-center gap-4">
setCurrentRow(0); // Reset to first row when switching tabs <ToggleGroup
}, []); 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
)}
const renderDatabaseGrid = useCallback( <div className="col-span-3 flex flex-1 flex-col gap-1">
() => (
<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="h-8" className="col-span-3 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">
@@ -131,55 +105,7 @@ export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
) : null} ) : null}
<ExampleOption /> <ExampleOption />
</div> </div>
</div> </ToggleGroup>
),
[
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,7 +69,6 @@ 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,14 +218,8 @@ export const CreateRelationshipDialog: React.FC<
closeCreateRelationshipDialog(); closeCreateRelationshipDialog();
} }
}} }}
modal={false}
> >
<DialogContent <DialogContent className="flex flex-col overflow-y-auto" showClose>
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,21 +17,15 @@ import { useDialog } from '@/hooks/use-dialog';
import { import {
exportBaseSQL, exportBaseSQL,
exportSQL, exportSQL,
} from '@/lib/data/sql-export/export-sql-script'; } from '@/lib/data/export-metadata/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;
@@ -42,8 +36,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
targetDatabaseType, targetDatabaseType,
}) => { }) => {
const { closeExportSQLDialog } = useDialog(); const { closeExportSQLDialog } = useDialog();
const { currentDiagram } = useChartDB(); const { currentDiagram, filteredSchemas } = 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);
@@ -55,16 +48,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const filteredDiagram: Diagram = { const filteredDiagram: Diagram = {
...currentDiagram, ...currentDiagram,
tables: currentDiagram.tables?.filter((table) => tables: currentDiagram.tables?.filter((table) =>
filterTable({ shouldShowTablesBySchemaFilter(table, filteredSchemas)
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(
@@ -76,20 +60,11 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return ( return (
sourceTable && sourceTable &&
targetTable && targetTable &&
filterRelationship({ shouldShowTablesBySchemaFilter(
tableA: { sourceTable,
id: sourceTable.id, filteredSchemas
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) => {
@@ -102,20 +77,11 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return ( return (
table && table &&
dependentTable && dependentTable &&
filterDependency({ shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
tableA: { shouldShowTablesBySchemaFilter(
id: table.id, dependentTable,
schema: table.schema, filteredSchemas
}, )
tableB: {
id: dependentTable.id,
schema: dependentTable.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
); );
}), }),
}; };
@@ -135,7 +101,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
signal: abortControllerRef.current?.signal, signal: abortControllerRef.current?.signal,
}); });
} }
}, [targetDatabaseType, currentDiagram, filter]); }, [targetDatabaseType, currentDiagram, filteredSchemas]);
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/data/import-metadata/import'; import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
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,8 +15,6 @@ 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;
@@ -26,7 +24,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
dialog, dialog,
databaseType, databaseType,
}) => { }) => {
const [importMethod, setImportMethod] = useState<ImportMethod>('query'); const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
const { closeImportDatabaseDialog } = useDialog(); const { closeImportDatabaseDialog } = useDialog();
const { showAlert } = useAlert(); const { showAlert } = useAlert();
const { const {
@@ -67,10 +65,6 @@ 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

@@ -0,0 +1,419 @@
import React, {
useCallback,
useEffect,
useState,
Suspense,
useRef,
} from 'react';
import * as monaco from 'monaco-editor';
import { useDialog } from '@/hooks/use-dialog';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { Button } from '@/components/button/button';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useTranslation } from 'react-i18next';
import { Editor } from '@/components/code-snippet/code-snippet';
import { useTheme } from '@/hooks/use-theme';
import { AlertCircle } from 'lucide-react';
import {
importDBMLToDiagram,
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';
interface DBMLError {
message: string;
line: number;
column: number;
}
function parseDBMLError(error: unknown): DBMLError | null {
try {
if (typeof error === 'string') {
const parsed = JSON.parse(error);
if (parsed.diags?.[0]) {
const diag = parsed.diags[0];
return {
message: diag.message,
line: diag.location.start.line,
column: diag.location.start.column,
};
}
} else if (error && typeof error === 'object' && 'diags' in error) {
const parsed = error as {
diags: Array<{
message: string;
location: { start: { line: number; column: number } };
}>;
};
if (parsed.diags?.[0]) {
return {
message: parsed.diags[0].message,
line: parsed.diags[0].location.start.line,
column: parsed.diags[0].location.start.column,
};
}
}
} catch (e) {
console.error('Error parsing DBML error:', e);
}
return null;
}
export interface ImportDBMLDialogProps extends BaseDialogProps {
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 highlightErrorLine = useCallback((error: DBMLError) => {
if (!editorRef.current) return;
const model = editorRef.current.getModel();
if (!model) return;
const decorations = [
{
range: new monaco.Range(
error.line,
1,
error.line,
model.getLineMaxColumn(error.line)
),
options: {
isWholeLine: true,
className: 'dbml-error-line',
glyphMarginClassName: 'dbml-error-glyph',
hoverMessage: { value: error.message },
overviewRuler: {
color: '#ff0000',
position: monaco.editor.OverviewRulerLane.Right,
darkColor: '#ff0000',
},
},
},
];
decorationsCollection.current?.set(decorations);
}, []);
const clearDecorations = useCallback(() => {
decorationsCollection.current?.clear();
}, []);
const validateDBML = useCallback(
async (content: string) => {
// Clear previous errors
setErrorMessage(undefined);
clearDecorations();
if (!content.trim()) return;
try {
const 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(parsedError);
} else {
setErrorMessage(
e instanceof Error ? e.message : JSON.stringify(e)
);
}
}
},
[clearDecorations, highlightErrorLine, t]
);
const debouncedValidateRef = useRef<((value: string) => void) | null>(null);
// Set up debounced validation
useEffect(() => {
debouncedValidateRef.current = debounce((value: string) => {
validateDBML(value);
}, 500);
return () => {
debouncedValidateRef.current = null;
};
}, [validateDBML]);
// Trigger validation when content changes
useEffect(() => {
if (debouncedValidateRef.current) {
debouncedValidateRef.current(dbmlContent);
}
}, [dbmlContent]);
useEffect(() => {
if (!dialog.open) {
setErrorMessage(undefined);
clearDecorations();
setDBMLContent(initialDBML);
}
}, [dialog.open, initialDBML, clearDecorations]);
const handleImport = useCallback(async () => {
if (!dbmlContent.trim() || errorMessage) return;
try {
const importedDiagram = await importDBMLToDiagram(dbmlContent);
const tableIdsToRemove = tables
.filter((table) =>
importedDiagram.tables?.some(
(t: 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

@@ -1,98 +0,0 @@
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,7 +27,6 @@ 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;
@@ -47,22 +46,21 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
string | undefined string | undefined
>(); >();
const fetchDiagrams = useCallback(async () => { useEffect(() => {
const diagrams = await listDiagrams({ includeTables: true }); setSelectedDiagramId(undefined);
setDiagrams( }, [dialog.open]);
diagrams.sort(
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
)
);
}, [listDiagrams]);
useEffect(() => { useEffect(() => {
if (!dialog.open) { const fetchDiagrams = async () => {
return; const diagrams = await listDiagrams({ includeTables: true });
} setDiagrams(
setSelectedDiagramId(undefined); diagrams.sort(
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
)
);
};
fetchDiagrams(); fetchDiagrams();
}, [dialog.open, fetchDiagrams]); }, [listDiagrams, setDiagrams, dialog.open]);
const openDiagram = useCallback( const openDiagram = useCallback(
(diagramId: string) => { (diagramId: string) => {
@@ -168,7 +166,6 @@ 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>
@@ -224,19 +221,6 @@ 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 } = useChartDB(); const { databaseType, filteredSchemas, filterSchemas } = useChartDB();
const [selectedSchemaId, setSelectedSchemaId] = useState<string>( const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
table?.schema table?.schema
? schemaNameToSchemaId(table.schema) ? schemaNameToSchemaId(table.schema)
@@ -93,6 +93,7 @@ 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()),
@@ -100,14 +101,30 @@ 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(
() => () =>

View File

@@ -155,29 +155,3 @@
background-size: 650%; background-size: 650%;
} }
} }
/* Edit button emphasis animation */
@keyframes dbml_edit-button-emphasis {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
background-color: rgba(59, 130, 246, 0);
}
50% {
transform: scale(1.1);
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
background-color: rgba(59, 130, 246, 0.1);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
background-color: rgba(59, 130, 246, 0);
}
}
.dbml-edit-button-emphasis {
animation: dbml_edit-button-emphasis 0.6s ease-in-out;
animation-iteration-count: 1;
position: relative;
z-index: 10;
}

View File

@@ -1,181 +0,0 @@
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

@@ -1,379 +0,0 @@
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

@@ -1,42 +0,0 @@
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,25 +2,17 @@ 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: {
actions: { file: {
actions: 'الإجراءات', file: 'ملف',
new: 'جديد...', new: 'جديد',
browse: 'تصفح...', open: 'فتح',
save: 'حفظ', save: 'حفظ',
import: 'استيراد قاعدة بيانات', import: 'استيراد قاعدة بيانات',
export_sql: 'SQL تصدير', export_sql: 'SQL تصدير',
export_as: 'تصدير كـ', export_as: 'تصدير كـ',
delete_diagram: 'حذف', delete_diagram: 'حذف الرسم البياني',
exit: 'خروج',
}, },
edit: { edit: {
edit: 'تحرير', edit: 'تحرير',
@@ -37,7 +29,6 @@ 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: 'إخفاء الاعتمادات',
@@ -74,13 +65,22 @@ 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,11 +115,14 @@ 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
@@ -145,7 +148,6 @@ export const ar: LanguageTranslation = {
field_actions: { field_actions: {
title: 'خصائص الحقل', title: 'خصائص الحقل',
unique: 'فريد', unique: 'فريد',
auto_increment: 'زيادة تلقائية',
comments: 'تعليقات', comments: 'تعليقات',
no_comments: 'لا يوجد تعليقات', no_comments: 'لا يوجد تعليقات',
delete_field: 'حذف الحقل', delete_field: 'حذف الحقل',
@@ -160,7 +162,6 @@ export const ar: LanguageTranslation = {
title: 'خصائص الفهرس', title: 'خصائص الفهرس',
name: 'الإسم', name: 'الإسم',
unique: 'فريد', unique: 'فريد',
index_type: 'نوع الفهرس',
delete_index: 'حذف الفهرس', delete_index: 'حذف الفهرس',
}, },
table_actions: { table_actions: {
@@ -177,15 +178,12 @@ export const ar: LanguageTranslation = {
description: 'أنشئ جدولاً للبدء', description: 'أنشئ جدولاً للبدء',
}, },
}, },
refs_section: { relationships_section: {
refs: 'المراجع',
filter: 'تصفية',
collapse: 'طي الكل',
add_relationship: 'إضافة علاقة',
relationships: 'العلاقات', relationships: 'العلاقات',
dependencies: 'الاعتمادات', filter: 'تصفية',
add_relationship: 'إضافة علاقة',
collapse: 'طي الكل',
relationship: { relationship: {
relationship: 'العلاقة',
primary: 'الجدول الأساسي', primary: 'الجدول الأساسي',
foreign: 'الجدول المرتبط', foreign: 'الجدول المرتبط',
cardinality: 'الكاردينالية', cardinality: 'الكاردينالية',
@@ -195,8 +193,16 @@ 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: 'حذف',
@@ -206,8 +212,8 @@ export const ar: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'لا توجد علاقات', title: 'لا توجد اعتمادات',
description: 'إنشاء علاقة للبدء', description: 'إنشاء اعتماد للبدء',
}, },
}, },
@@ -232,33 +238,6 @@ 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',
@@ -275,7 +254,6 @@ 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',
@@ -298,7 +276,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',
@@ -335,13 +313,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: 'الإسم',
@@ -351,12 +329,6 @@ export const ar: LanguageTranslation = {
}, },
cancel: 'إلغاء', cancel: 'إلغاء',
open: 'فتح', open: 'فتح',
diagram_actions: {
open: 'فتح',
duplicate: 'تكرار',
delete: 'حذف',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -502,11 +474,9 @@ 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: {
@@ -525,8 +495,6 @@ export const ar: LanguageTranslation = {
language_select: { language_select: {
change_language: 'اللغة', change_language: 'اللغة',
}, },
on: 'تشغيل',
off: 'إيقاف',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'কার্য', file: 'ফাইল',
new: 'নতুন...', new: 'নতুন',
browse: 'ব্রাউজ করুন...', open: 'খুলুন',
save: 'সংরক্ষণ করুন', save: 'সংরক্ষণ করুন',
import: 'ডাটাবেস আমদানি করুন', import: 'ডাটাবেস আমদানি করুন',
export_sql: 'SQL রপ্তানি করুন', export_sql: 'SQL রপ্তানি করুন',
export_as: 'রূপে রপ্তানি করুন', export_as: 'রূপে রপ্তানি করুন',
delete_diagram: 'মুছুন', delete_diagram: 'ডায়াগ্রাম মুছুন',
exit: 'প্রস্থান করুন',
}, },
edit: { edit: {
edit: 'সম্পাদনা', edit: 'সম্পাদনা',
@@ -37,7 +29,6 @@ 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: 'নির্ভরতাগুলি লুকান',
@@ -75,13 +66,22 @@ 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,11 +116,14 @@ 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
@@ -146,7 +149,6 @@ export const bn: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ফিল্ড কর্ম', title: 'ফিল্ড কর্ম',
unique: 'অদ্বিতীয়', unique: 'অদ্বিতীয়',
auto_increment: 'স্বয়ংক্রিয় বৃদ্ধি',
comments: 'মন্তব্য', comments: 'মন্তব্য',
no_comments: 'কোনো মন্তব্য নেই', no_comments: 'কোনো মন্তব্য নেই',
delete_field: 'ফিল্ড মুছুন', delete_field: 'ফিল্ড মুছুন',
@@ -162,7 +164,6 @@ export const bn: LanguageTranslation = {
title: 'ইনডেক্স কর্ম', title: 'ইনডেক্স কর্ম',
name: 'নাম', name: 'নাম',
unique: 'অদ্বিতীয়', unique: 'অদ্বিতীয়',
index_type: 'ইনডেক্স ধরন',
delete_index: 'ইনডেক্স মুছুন', delete_index: 'ইনডেক্স মুছুন',
}, },
table_actions: { table_actions: {
@@ -179,17 +180,14 @@ export const bn: LanguageTranslation = {
description: 'শুরু করতে একটি টেবিল তৈরি করুন', description: 'শুরু করতে একটি টেবিল তৈরি করুন',
}, },
}, },
refs_section: { relationships_section: {
refs: 'রেফস',
filter: 'ফিল্টার',
collapse: 'সব ভাঁজ করুন',
add_relationship: 'সম্পর্ক যোগ করুন',
relationships: 'সম্পর্ক', relationships: 'সম্পর্ক',
dependencies: 'নির্ভরতাগুলি', filter: 'ফিল্টার',
add_relationship: 'সম্পর্ক যোগ করুন',
collapse: 'সব ভাঁজ করুন',
relationship: { relationship: {
relationship: 'সম্পর্ক',
primary: 'প্রাথমিক টেবিল', primary: 'প্রাথমিক টেবিল',
foreign: 'রেফারেন্স করা টেবিল', foreign: 'বিদেশি টেবিল',
cardinality: 'কার্ডিনালিটি', cardinality: 'কার্ডিনালিটি',
delete_relationship: 'মুছুন', delete_relationship: 'মুছুন',
relationship_actions: { relationship_actions: {
@@ -197,19 +195,27 @@ 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: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।',
}, },
}, },
@@ -233,35 +239,6 @@ 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',
@@ -278,7 +255,6 @@ 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',
@@ -301,7 +277,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
@@ -339,13 +315,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: 'নাম',
@@ -355,12 +331,6 @@ export const bn: LanguageTranslation = {
}, },
cancel: 'বাতিল করুন', cancel: 'বাতিল করুন',
open: 'খুলুন', open: 'খুলুন',
diagram_actions: {
open: 'খুলুন',
duplicate: 'ডুপ্লিকেট',
delete: 'মুছুন',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -509,11 +479,9 @@ 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: {
@@ -532,9 +500,6 @@ export const bn: LanguageTranslation = {
language_select: { language_select: {
change_language: 'ভাষা পরিবর্তন করুন', change_language: 'ভাষা পরিবর্তন করুন',
}, },
on: 'চালু',
off: 'বন্ধ',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Aktionen', file: 'Datei',
new: 'Neu...', new: 'Neu',
browse: 'Durchsuchen...', open: 'Öffnen',
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: 'Löschen', delete_diagram: 'Diagramm löschen',
exit: 'Beenden',
}, },
edit: { edit: {
edit: 'Bearbeiten', edit: 'Bearbeiten',
@@ -37,7 +29,6 @@ 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',
@@ -75,13 +66,22 @@ export const de: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Diagramm automatisch anordnen', title: 'Diagramm neu 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: 'Automatisch anordnen', reorder: 'Neu 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,11 +117,14 @@ 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
@@ -147,7 +150,6 @@ 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',
@@ -163,7 +165,6 @@ 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: {
@@ -180,26 +181,32 @@ export const de: LanguageTranslation = {
description: 'Erstellen Sie eine Tabelle, um zu beginnen', description: 'Erstellen Sie eine Tabelle, um zu beginnen',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Filter',
collapse: 'Alle einklappen',
add_relationship: 'Beziehung hinzufügen',
relationships: 'Beziehungen', relationships: 'Beziehungen',
dependencies: 'Abhängigkeiten', filter: 'Filter',
add_relationship: 'Beziehung hinzufügen',
collapse: 'Alle einklappen',
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: 'Löschen', delete_relationship: 'Beziehung löschen',
relationship_actions: { relationship_actions: {
title: 'Aktionen', title: 'Aktionen',
delete_relationship: 'Löschen', delete_relationship: 'Beziehung 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',
@@ -209,8 +216,8 @@ export const de: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Keine Beziehungen', title: 'Keine Abhängigkeiten',
description: 'Erstellen Sie eine Beziehung, um zu beginnen', description: 'Erstellen Sie eine Ansicht, um zu beginnen',
}, },
}, },
@@ -234,35 +241,6 @@ 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',
@@ -279,7 +257,6 @@ 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',
@@ -302,7 +279,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 automatisch anordnen', reorder_diagram: 'Diagramm neu anordnen',
// TODO: Translate // TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"', clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
@@ -342,13 +319,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: 'Leere Datenbank', empty_diagram: 'Leeres Diagramm',
continue: 'Weiter', continue: 'Weiter',
import: 'Importieren', import: 'Importieren',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Datenbank öffnen', title: 'Diagramm ö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',
@@ -358,12 +335,6 @@ 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: {
@@ -512,11 +483,9 @@ 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: {
@@ -536,9 +505,6 @@ export const de: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Sprache', change_language: 'Sprache',
}, },
on: 'Ein',
off: 'Aus',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Actions', file: 'File',
new: 'New...', new: 'New',
browse: 'Browse...', open: 'Open',
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', delete_diagram: 'Delete Diagram',
exit: 'Exit',
}, },
edit: { edit: {
edit: 'Edit', edit: 'Edit',
@@ -37,7 +29,6 @@ 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',
@@ -73,13 +64,21 @@ export const en = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Auto Arrange Diagram', title: 'Reorder 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: 'Auto Arrange', reorder: 'Reorder',
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',
@@ -114,11 +113,14 @@ 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',
@@ -142,7 +144,6 @@ 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',
@@ -156,7 +157,6 @@ 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,15 +173,12 @@ export const en = {
description: 'Create a table to get started', description: 'Create a table to get started',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Filter',
collapse: 'Collapse All',
add_relationship: 'Add Relationship',
relationships: 'Relationships', relationships: 'Relationships',
dependencies: 'Dependencies', filter: 'Filter',
add_relationship: 'Add Relationship',
collapse: 'Collapse All',
relationship: { relationship: {
relationship: 'Relationship',
primary: 'Primary Table', primary: 'Primary Table',
foreign: 'Referenced Table', foreign: 'Referenced Table',
cardinality: 'Cardinality', cardinality: 'Cardinality',
@@ -191,8 +188,16 @@ 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',
@@ -202,8 +207,8 @@ export const en = {
}, },
}, },
empty_state: { empty_state: {
title: 'No relationships', title: 'No dependencies',
description: 'Create a relationship to get started', description: 'Create a view to get started',
}, },
}, },
@@ -227,34 +232,6 @@ 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',
@@ -270,7 +247,6 @@ 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',
@@ -293,7 +269,7 @@ export const en = {
show_all: 'Show All', show_all: 'Show All',
undo: 'Undo', undo: 'Undo',
redo: 'Redo', redo: 'Redo',
reorder_diagram: 'Auto Arrange Diagram', reorder_diagram: 'Reorder 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:
@@ -329,13 +305,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 database', empty_diagram: 'Empty diagram',
continue: 'Continue', continue: 'Continue',
import: 'Import', import: 'Import',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Open Database', title: 'Open Diagram',
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',
@@ -345,12 +321,6 @@ 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: {
@@ -498,10 +468,8 @@ 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: {
@@ -520,9 +488,6 @@ export const en = {
language_select: { language_select: {
change_language: 'Language', change_language: 'Language',
}, },
on: 'On',
off: 'Off',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Acciones', file: 'Archivo',
new: 'Nuevo...', new: 'Nuevo',
browse: 'Examinar...', open: 'Abrir',
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', delete_diagram: 'Eliminar Diagrama',
exit: 'Salir',
}, },
edit: { edit: {
edit: 'Editar', edit: 'Editar',
@@ -37,7 +29,6 @@ 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',
@@ -74,10 +65,10 @@ export const es: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Organizar Diagrama Automáticamente', title: 'Reordenar Diagrama',
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: 'Organizar Automáticamente', reorder: 'Reordenar',
cancel: 'Cancelar', cancel: 'Cancelar',
}, },
@@ -115,11 +106,14 @@ 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
@@ -145,7 +139,6 @@ 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',
@@ -161,7 +154,6 @@ 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: {
@@ -178,17 +170,14 @@ export const es: LanguageTranslation = {
description: 'Crea una tabla para comenzar', description: 'Crea una tabla para comenzar',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs', relationships: 'Relaciones',
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: {
relationship: 'Relación', primary: 'Primaria',
primary: 'Tabla Primaria', foreign: 'Foránea',
foreign: 'Tabla Referenciada',
cardinality: 'Cardinalidad', cardinality: 'Cardinalidad',
delete_relationship: 'Eliminar', delete_relationship: 'Eliminar',
relationship_actions: { relationship_actions: {
@@ -196,10 +185,18 @@ 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',
@@ -207,8 +204,8 @@ export const es: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Sin relaciones', title: 'Sin dependencias',
description: 'Crea una relación para comenzar', description: 'Crea una vista para comenzar',
}, },
}, },
@@ -232,35 +229,6 @@ 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',
@@ -277,7 +245,6 @@ 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',
@@ -300,7 +267,7 @@ export const es: LanguageTranslation = {
show_all: 'Mostrar Todo', show_all: 'Mostrar Todo',
undo: 'Deshacer', undo: 'Deshacer',
redo: 'Rehacer', redo: 'Rehacer',
reorder_diagram: 'Organizar Diagrama Automáticamente', reorder_diagram: 'Reordenar Diagrama',
// 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:
@@ -339,13 +306,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: 'Base de datos vacía', empty_diagram: 'Diagrama vacío',
continue: 'Continuar', continue: 'Continuar',
import: 'Importar', import: 'Importar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Abrir Base de Datos', title: 'Abrir Diagrama',
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: {
@@ -356,12 +323,6 @@ 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: {
@@ -463,6 +424,14 @@ 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',
@@ -511,11 +480,9 @@ 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: {
@@ -535,9 +502,6 @@ export const es: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Idioma', change_language: 'Idioma',
}, },
on: 'Encendido',
off: 'Apagado',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Actions', file: 'Fichier',
new: 'Nouveau...', new: 'Nouveau',
browse: 'Parcourir...', open: 'Ouvrir',
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', delete_diagram: 'Supprimer le Diagramme',
exit: 'Quitter',
}, },
edit: { edit: {
edit: 'Édition', edit: 'Édition',
@@ -37,7 +29,6 @@ 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',
@@ -73,10 +64,10 @@ export const fr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Organiser Automatiquement le Diagramme', title: 'Réorganiser 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: 'Organiser Automatiquement', reorder: 'Réorganiser',
cancel: 'Annuler', cancel: 'Annuler',
}, },
@@ -114,11 +105,14 @@ 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',
@@ -143,7 +137,6 @@ 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',
@@ -159,7 +152,6 @@ 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: {
@@ -176,15 +168,12 @@ export const fr: LanguageTranslation = {
description: 'Créez une table pour commencer', description: 'Créez une table pour commencer',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Filtrer',
collapse: 'Réduire Tout',
add_relationship: 'Ajouter une Relation',
relationships: 'Relations', relationships: 'Relations',
dependencies: 'Dépendances', filter: 'Filtrer',
add_relationship: 'Ajouter une Relation',
collapse: 'Réduire Tout',
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é',
@@ -194,8 +183,16 @@ 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',
@@ -205,8 +202,8 @@ export const fr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Aucune relation', title: 'Aucune dépendance',
description: 'Créez une relation pour commencer', description: 'Créez une vue pour commencer',
}, },
}, },
@@ -230,35 +227,6 @@ 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',
@@ -275,7 +243,6 @@ 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',
@@ -298,7 +265,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: 'Organiser Automatiquement le Diagramme', reorder_diagram: 'Réorganiser 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:
@@ -336,13 +303,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: 'Base de données vide', empty_diagram: 'Diagramme vide',
continue: 'Continuer', continue: 'Continuer',
import: 'Importer', import: 'Importer',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Ouvrir Base de Données', title: 'Ouvrir Diagramme',
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: {
@@ -353,12 +320,6 @@ 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: {
@@ -396,6 +357,15 @@ 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:
@@ -507,11 +477,9 @@ 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: {
@@ -531,9 +499,6 @@ export const fr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Langue', change_language: 'Langue',
}, },
on: 'Activé',
off: 'Désactivé',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'ક્રિયાઓ', file: 'ફાઇલ',
new: 'નવું...', new: 'નવું',
browse: 'બ્રાઉજ કરો...', open: 'ખોલો',
save: 'સાચવો', save: 'સાચવો',
import: 'ડેટાબેસ આયાત કરો', import: 'ડેટાબેસ આયાત કરો',
export_sql: 'SQL નિકાસ કરો', export_sql: 'SQL નિકાસ કરો',
export_as: 'રૂપે નિકાસ કરો', export_as: 'રૂપે નિકાસ કરો',
delete_diagram: 'કાઢી નાખો', delete_diagram: 'ડાયાગ્રામ કાઢી નાખો',
exit: 'બહાર જાઓ',
}, },
edit: { edit: {
edit: 'ફેરફાર', edit: 'ફેરફાર',
@@ -37,7 +29,6 @@ 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: 'નિર્ભરતાઓ છુપાવો',
@@ -75,13 +66,22 @@ 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,11 +116,14 @@ 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
@@ -147,7 +150,6 @@ export const gu: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ફીલ્ડ લક્ષણો', title: 'ફીલ્ડ લક્ષણો',
unique: 'અદ્વિતીય', unique: 'અદ્વિતીય',
auto_increment: 'ઑટો ઇન્ક્રિમેન્ટ',
comments: 'ટિપ્પણીઓ', comments: 'ટિપ્પણીઓ',
no_comments: 'કોઈ ટિપ્પણીઓ નથી', no_comments: 'કોઈ ટિપ્પણીઓ નથી',
delete_field: 'ફીલ્ડ કાઢી નાખો', delete_field: 'ફીલ્ડ કાઢી નાખો',
@@ -163,7 +165,6 @@ export const gu: LanguageTranslation = {
title: 'ઇન્ડેક્સ લક્ષણો', title: 'ઇન્ડેક્સ લક્ષણો',
name: 'નામ', name: 'નામ',
unique: 'અદ્વિતીય', unique: 'અદ્વિતીય',
index_type: 'ઇન્ડેક્સ પ્રકાર',
delete_index: 'ઇન્ડેક્સ કાઢી નાખો', delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
}, },
table_actions: { table_actions: {
@@ -180,17 +181,14 @@ export const gu: LanguageTranslation = {
description: 'શરૂ કરવા માટે એક ટેબલ બનાવો', description: 'શરૂ કરવા માટે એક ટેબલ બનાવો',
}, },
}, },
refs_section: { relationships_section: {
refs: 'રેફ્સ',
filter: 'ફિલ્ટર',
collapse: 'બધાને સકુચિત કરો',
add_relationship: 'સંબંધ ઉમેરો',
relationships: 'સંબંધો', relationships: 'સંબંધો',
dependencies: 'નિર્ભરતાઓ', filter: 'ફિલ્ટર',
add_relationship: 'સંબંધ ઉમેરો',
collapse: 'બધાને સકુચિત કરો',
relationship: { relationship: {
relationship: 'સંબંધ',
primary: 'પ્રાથમિક ટેબલ', primary: 'પ્રાથમિક ટેબલ',
foreign: 'સંદર્ભિત ટેબલ', foreign: 'સંદર્ભ ટેબલ',
cardinality: 'કાર્ડિનાલિટી', cardinality: 'કાર્ડિનાલિટી',
delete_relationship: 'કાઢી નાખો', delete_relationship: 'કાઢી નાખો',
relationship_actions: { relationship_actions: {
@@ -198,19 +196,27 @@ 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: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.',
}, },
}, },
@@ -234,35 +240,6 @@ 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',
@@ -279,7 +256,6 @@ 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',
@@ -302,7 +278,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:
@@ -339,13 +315,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: 'નામ',
@@ -355,12 +331,6 @@ export const gu: LanguageTranslation = {
}, },
cancel: 'રદ કરો', cancel: 'રદ કરો',
open: 'ખોલો', open: 'ખોલો',
diagram_actions: {
open: 'ખોલો',
duplicate: 'ડુપ્લિકેટ',
delete: 'કાઢી નાખો',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -510,11 +480,9 @@ 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: {
@@ -533,9 +501,6 @@ export const gu: LanguageTranslation = {
language_select: { language_select: {
change_language: 'ભાષા બદલો', change_language: 'ભાષા બદલો',
}, },
on: 'ચાલુ',
off: 'બંધ',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'कार्य', file: 'फ़ाइल',
new: 'नया...', new: 'नया',
browse: 'ब्राउज़ करें...', open: 'खोलें',
save: 'सहेजें', save: 'सहेजें',
import: 'डेटाबेस आयात करें', import: 'डेटाबेस आयात करें',
export_sql: 'SQL निर्यात करें', export_sql: 'SQL निर्यात करें',
export_as: 'के रूप में निर्यात करें', export_as: 'के रूप में निर्यात करें',
delete_diagram: 'हटाएँ', delete_diagram: 'आरेख हटाएँ',
exit: 'बाहर जाएँ',
}, },
edit: { edit: {
edit: 'संपादित करें', edit: 'संपादित करें',
@@ -37,7 +29,6 @@ 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: 'निर्भरता छिपाएँ',
@@ -74,13 +65,22 @@ 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,11 +116,14 @@ 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
@@ -146,7 +149,6 @@ export const hi: LanguageTranslation = {
field_actions: { field_actions: {
title: 'फ़ील्ड विशेषताएँ', title: 'फ़ील्ड विशेषताएँ',
unique: 'अद्वितीय', unique: 'अद्वितीय',
auto_increment: 'ऑटो इंक्रीमेंट',
comments: 'टिप्पणियाँ', comments: 'टिप्पणियाँ',
no_comments: 'कोई टिप्पणी नहीं', no_comments: 'कोई टिप्पणी नहीं',
delete_field: 'फ़ील्ड हटाएँ', delete_field: 'फ़ील्ड हटाएँ',
@@ -162,7 +164,6 @@ export const hi: LanguageTranslation = {
title: 'सूचकांक विशेषताएँ', title: 'सूचकांक विशेषताएँ',
name: 'नाम', name: 'नाम',
unique: 'अद्वितीय', unique: 'अद्वितीय',
index_type: 'इंडेक्स प्रकार',
delete_index: 'सूचकांक हटाएँ', delete_index: 'सूचकांक हटाएँ',
}, },
table_actions: { table_actions: {
@@ -179,15 +180,12 @@ export const hi: LanguageTranslation = {
description: 'शुरू करने के लिए एक तालिका बनाएँ', description: 'शुरू करने के लिए एक तालिका बनाएँ',
}, },
}, },
refs_section: { relationships_section: {
refs: 'रेफ्स',
filter: 'फ़िल्टर',
collapse: 'सभी को संक्षिप्त करें',
add_relationship: 'संबंध जोड़ें',
relationships: 'संबंध', relationships: 'संबंध',
dependencies: 'निर्भरताएँ', filter: 'फ़िल्टर',
add_relationship: 'संबंध जोड़ें',
collapse: 'सभी को संक्षिप्त करें',
relationship: { relationship: {
relationship: 'संबंध',
primary: 'प्राथमिक तालिका', primary: 'प्राथमिक तालिका',
foreign: 'संदर्भित तालिका', foreign: 'संदर्भित तालिका',
cardinality: 'कार्डिनैलिटी', cardinality: 'कार्डिनैलिटी',
@@ -197,19 +195,28 @@ 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: 'इस अनुभाग में कोई निर्भरता उपलब्ध नहीं है।',
}, },
}, },
@@ -233,35 +240,6 @@ 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',
@@ -278,7 +256,6 @@ 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',
@@ -301,7 +278,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:
@@ -341,13 +318,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: 'नाम',
@@ -357,12 +334,6 @@ export const hi: LanguageTranslation = {
}, },
cancel: 'रद्द करें', cancel: 'रद्द करें',
open: 'खोलें', open: 'खोलें',
diagram_actions: {
open: 'खोलें',
duplicate: 'डुप्लिकेट',
delete: 'हटाएं',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -512,11 +483,9 @@ 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: {
@@ -536,9 +505,6 @@ export const hi: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा बदलें', change_language: 'भाषा बदलें',
}, },
on: 'चालू',
off: 'बंद',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Akcije', file: 'Datoteka',
new: 'Novi...', new: 'Nova',
browse: 'Pregledaj...', open: 'Otvori',
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', delete_diagram: 'Izbriši dijagram',
exit: 'Izađi',
}, },
edit: { edit: {
edit: 'Uredi', edit: 'Uredi',
@@ -37,7 +29,6 @@ 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',
@@ -73,13 +64,21 @@ export const hr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Automatski preuredi dijagram', title: '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: 'Automatski preuredi', reorder: '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',
@@ -114,11 +113,14 @@ 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',
@@ -143,7 +145,6 @@ 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',
@@ -157,7 +158,6 @@ 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,15 +174,12 @@ export const hr: LanguageTranslation = {
description: 'Stvorite tablicu za početak', description: 'Stvorite tablicu za početak',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Filtriraj',
collapse: 'Sažmi sve',
add_relationship: 'Dodaj vezu',
relationships: 'Veze', relationships: 'Veze',
dependencies: 'Ovisnosti', filter: 'Filtriraj',
add_relationship: 'Dodaj vezu',
collapse: 'Sažmi sve',
relationship: { relationship: {
relationship: 'Veza',
primary: 'Primarna tablica', primary: 'Primarna tablica',
foreign: 'Referentna tablica', foreign: 'Referentna tablica',
cardinality: 'Kardinalnost', cardinality: 'Kardinalnost',
@@ -192,8 +189,16 @@ 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',
@@ -203,8 +208,8 @@ export const hr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Nema veze', title: 'Nema ovisnosti',
description: 'Stvorite vezu za početak', description: 'Stvorite pogled za početak',
}, },
}, },
@@ -229,34 +234,6 @@ 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',
@@ -273,7 +250,6 @@ 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',
@@ -297,7 +273,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: 'Automatski preuredi dijagram', reorder_diagram: '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:
@@ -333,13 +309,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: 'Prazna baza podataka', empty_diagram: 'Prazan dijagram',
continue: 'Nastavi', continue: 'Nastavi',
import: 'Uvezi', import: 'Uvezi',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Otvori bazu podataka', title: 'Otvori dijagram',
description: 'Odaberite dijagram za otvaranje iz popisa ispod.', description: 'Odaberite dijagram za otvaranje iz popisa ispod.',
table_columns: { table_columns: {
name: 'Naziv', name: 'Naziv',
@@ -349,12 +325,6 @@ 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: {
@@ -503,10 +473,8 @@ 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: {
@@ -525,9 +493,6 @@ export const hr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Jezik', change_language: 'Jezik',
}, },
on: 'Uključeno',
off: 'Isključeno',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Aksi', file: 'Berkas',
new: 'Baru...', new: 'Buat Baru',
browse: 'Jelajahi...', open: 'Buka',
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', delete_diagram: 'Hapus Diagram',
exit: 'Keluar',
}, },
edit: { edit: {
edit: 'Ubah', edit: 'Ubah',
@@ -37,7 +29,6 @@ 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',
@@ -74,13 +65,22 @@ export const id_ID: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Atur Otomatis Diagram', title: 'Atur Ulang 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 Otomatis', reorder: 'Atur Ulang',
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,11 +115,14 @@ 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
@@ -145,7 +148,6 @@ 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',
@@ -161,7 +163,6 @@ 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: {
@@ -178,15 +179,12 @@ export const id_ID: LanguageTranslation = {
description: 'Buat tabel untuk memulai', description: 'Buat tabel untuk memulai',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Saring',
collapse: 'Lipat Semua',
add_relationship: 'Tambah Hubungan',
relationships: 'Hubungan', relationships: 'Hubungan',
dependencies: 'Dependensi', filter: 'Saring',
add_relationship: 'Tambah Hubungan',
collapse: 'Lipat Semua',
relationship: { relationship: {
relationship: 'Hubungan',
primary: 'Tabel Primer', primary: 'Tabel Primer',
foreign: 'Tabel Referensi', foreign: 'Tabel Referensi',
cardinality: 'Kardinalitas', cardinality: 'Kardinalitas',
@@ -196,8 +194,16 @@ 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',
@@ -207,8 +213,8 @@ export const id_ID: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Tidak ada hubungan', title: 'Tidak ada dependensi',
description: 'Buat hubungan untuk memulai', description: 'Buat tampilan untuk memulai',
}, },
}, },
@@ -232,35 +238,6 @@ 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',
@@ -277,7 +254,6 @@ 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',
@@ -300,7 +276,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 Otomatis Diagram', reorder_diagram: 'Atur Ulang 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:
@@ -338,13 +314,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: 'Database Kosong', empty_diagram: 'Diagram Kosong',
continue: 'Lanjutkan', continue: 'Lanjutkan',
import: 'Impor', import: 'Impor',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Buka Database', title: 'Buka Diagram',
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',
@@ -354,12 +330,6 @@ 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: {
@@ -509,11 +479,9 @@ 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: {
@@ -532,9 +500,6 @@ export const id_ID: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Bahasa', change_language: 'Bahasa',
}, },
on: 'Aktif',
off: 'Nonaktif',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'アクション', file: 'ファイル',
new: '新規...', new: '新規',
browse: '参照...', open: '開く',
save: '保存', save: '保存',
import: 'データベースをインポート', import: 'データベースをインポート',
export_sql: 'SQLをエクスポート', export_sql: 'SQLをエクスポート',
export_as: '形式を指定してエクスポート', export_as: '形式を指定してエクスポート',
delete_diagram: '削除', delete_diagram: 'ダイアグラムを削除',
exit: '終了',
}, },
edit: { edit: {
edit: '編集', edit: '編集',
@@ -37,7 +29,6 @@ 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',
@@ -76,13 +67,22 @@ 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,11 +119,14 @@ 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
@@ -149,7 +152,6 @@ export const ja: LanguageTranslation = {
field_actions: { field_actions: {
title: 'フィールド属性', title: 'フィールド属性',
unique: 'ユニーク', unique: 'ユニーク',
auto_increment: 'オートインクリメント',
comments: 'コメント', comments: 'コメント',
no_comments: 'コメントがありません', no_comments: 'コメントがありません',
delete_field: 'フィールドを削除', delete_field: 'フィールドを削除',
@@ -165,7 +167,6 @@ export const ja: LanguageTranslation = {
title: 'インデックス属性', title: 'インデックス属性',
name: '名前', name: '名前',
unique: 'ユニーク', unique: 'ユニーク',
index_type: 'インデックスタイプ',
delete_index: 'インデックスを削除', delete_index: 'インデックスを削除',
}, },
table_actions: { table_actions: {
@@ -182,15 +183,12 @@ export const ja: LanguageTranslation = {
description: 'テーブルを作成して開始してください', description: 'テーブルを作成して開始してください',
}, },
}, },
refs_section: { relationships_section: {
refs: '参照',
filter: 'フィルタ',
collapse: 'すべて折りたたむ',
add_relationship: 'リレーションシップを追加',
relationships: 'リレーションシップ', relationships: 'リレーションシップ',
dependencies: '依存関係', filter: 'フィルタ',
add_relationship: 'リレーションシップを追加',
collapse: 'すべて折りたたむ',
relationship: { relationship: {
relationship: 'リレーションシップ',
primary: '主テーブル', primary: '主テーブル',
foreign: '参照テーブル', foreign: '参照テーブル',
cardinality: 'カーディナリティ', cardinality: 'カーディナリティ',
@@ -200,20 +198,29 @@ export const ja: LanguageTranslation = {
delete_relationship: '削除', delete_relationship: '削除',
}, },
}, },
dependency: {
dependency: '依存関係',
table: 'テーブル',
dependent_table: '依存ビュー',
delete_dependency: '削除',
dependency_actions: {
title: '操作',
delete_dependency: '削除',
},
},
empty_state: { empty_state: {
title: 'リレーションシップがありません', title: 'リレーションシップがありません',
description: description:
'開始するためにリレーションシップを作成してください', 'テーブルを接続するためにリレーションシップを作成してください',
},
},
// TODO: Translate
dependencies_section: {
dependencies: 'Dependencies',
filter: 'Filter',
collapse: 'Collapse All',
dependency: {
table: 'Table',
dependent_table: 'Dependent View',
delete_dependency: 'Delete',
dependency_actions: {
title: 'Actions',
delete_dependency: 'Delete',
},
},
empty_state: {
title: 'No dependencies',
description: 'Create a view to get started',
}, },
}, },
@@ -237,35 +244,6 @@ 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',
@@ -282,7 +260,6 @@ 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',
@@ -305,7 +282,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}}"',
@@ -343,13 +320,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: '名前',
@@ -359,12 +336,6 @@ export const ja: LanguageTranslation = {
}, },
cancel: 'キャンセル', cancel: 'キャンセル',
open: '開く', open: '開く',
diagram_actions: {
open: '開く',
duplicate: '複製',
delete: '削除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -514,11 +485,9 @@ 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: {
@@ -538,9 +507,6 @@ export const ja: LanguageTranslation = {
language_select: { language_select: {
change_language: '言語', change_language: '言語',
}, },
on: 'オン',
off: 'オフ',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: '작업', file: '파일',
new: '새로 만들기...', new: '새 다이어그램',
browse: '찾아보기...', open: '열기',
save: '저장', save: '저장',
import: '데이터베이스 가져오기', import: '데이터베이스 가져오기',
export_sql: 'SQL로 저장', export_sql: 'SQL로 저장',
export_as: '다른 형식으로 저장', export_as: '다른 형식으로 저장',
delete_diagram: '삭제', delete_diagram: '다이어그램 삭제',
exit: '종료',
}, },
edit: { edit: {
edit: '편집', edit: '편집',
@@ -37,7 +29,6 @@ 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: '종속성 숨기기',
@@ -74,13 +65,22 @@ 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,11 +115,14 @@ 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
@@ -145,7 +148,6 @@ 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: '필드 삭제',
@@ -161,7 +163,6 @@ export const ko_KR: LanguageTranslation = {
title: '인덱스 속성', title: '인덱스 속성',
name: '인덱스 명', name: '인덱스 명',
unique: '유니크 여부', unique: '유니크 여부',
index_type: '인덱스 타입',
delete_index: '인덱스 삭제', delete_index: '인덱스 삭제',
}, },
table_actions: { table_actions: {
@@ -178,15 +179,12 @@ export const ko_KR: LanguageTranslation = {
description: '테이블을 만들어 시작하세요.', description: '테이블을 만들어 시작하세요.',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: '필터',
collapse: '모두 접기',
add_relationship: '연관 관계 추가',
relationships: '연관 관계', relationships: '연관 관계',
dependencies: '종속성', filter: '필터',
add_relationship: '연관 관계 추가',
collapse: '모두 접기',
relationship: { relationship: {
relationship: '연관 관계',
primary: '주 테이블', primary: '주 테이블',
foreign: '참조 테이블', foreign: '참조 테이블',
cardinality: '카디널리티', cardinality: '카디널리티',
@@ -196,8 +194,16 @@ 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: '삭제',
@@ -207,8 +213,8 @@ export const ko_KR: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '연관 관계 없음', title: '뷰 테이블 없음',
description: '연관 관계를 만들어 시작하세요.', description: '뷰 테이블을 만들어 시작하세요.',
}, },
}, },
@@ -232,35 +238,6 @@ 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',
@@ -277,7 +254,6 @@ 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',
@@ -300,7 +276,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:
@@ -338,13 +314,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: '이름',
@@ -354,12 +330,6 @@ export const ko_KR: LanguageTranslation = {
}, },
cancel: '취소', cancel: '취소',
open: '열기', open: '열기',
diagram_actions: {
open: '열기',
duplicate: '복제',
delete: '삭제',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -506,11 +476,9 @@ 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: {
@@ -529,9 +497,6 @@ export const ko_KR: LanguageTranslation = {
language_select: { language_select: {
change_language: '언어', change_language: '언어',
}, },
on: '켜기',
off: '끄기',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'क्रिया', file: 'फाइल',
new: 'नवीन...', new: 'नवीन',
browse: 'ब्राउज करा...', open: 'उघडा',
save: 'जतन करा', save: 'जतन करा',
import: 'डेटाबेस इम्पोर्ट करा', import: 'डेटाबेस इम्पोर्ट करा',
export_sql: 'SQL एक्स्पोर्ट करा', export_sql: 'SQL एक्स्पोर्ट करा',
export_as: 'म्हणून एक्स्पोर्ट करा', export_as: 'म्हणून एक्स्पोर्ट करा',
delete_diagram: 'हटवा', delete_diagram: 'आरेख हटवा',
exit: 'बाहेर पडा',
}, },
edit: { edit: {
edit: 'संपादन करा', edit: 'संपादन करा',
@@ -37,7 +29,6 @@ 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: 'डिपेंडेन्सि लपवा',
@@ -75,13 +66,22 @@ 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,11 +118,14 @@ 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
@@ -148,7 +151,6 @@ export const mr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'फील्ड गुणधर्म', title: 'फील्ड गुणधर्म',
unique: 'युनिक', unique: 'युनिक',
auto_increment: 'ऑटो इंक्रिमेंट',
comments: 'टिप्पण्या', comments: 'टिप्पण्या',
no_comments: 'कोणत्याही टिप्पणी नाहीत', no_comments: 'कोणत्याही टिप्पणी नाहीत',
delete_field: 'फील्ड हटवा', delete_field: 'फील्ड हटवा',
@@ -164,7 +166,6 @@ export const mr: LanguageTranslation = {
title: 'इंडेक्स गुणधर्म', title: 'इंडेक्स गुणधर्म',
name: 'नाव', name: 'नाव',
unique: 'युनिक', unique: 'युनिक',
index_type: 'इंडेक्स प्रकार',
delete_index: 'इंडेक्स हटवा', delete_index: 'इंडेक्स हटवा',
}, },
table_actions: { table_actions: {
@@ -182,15 +183,12 @@ export const mr: LanguageTranslation = {
description: 'सुरू करण्यासाठी एक टेबल तयार करा', description: 'सुरू करण्यासाठी एक टेबल तयार करा',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'फिल्टर',
collapse: 'सर्व संकुचित करा',
add_relationship: 'रिलेशनशिप जोडा',
relationships: 'रिलेशनशिप', relationships: 'रिलेशनशिप',
dependencies: 'डिपेंडेन्सि', filter: 'फिल्टर',
add_relationship: 'रिलेशनशिप जोडा',
collapse: 'सर्व संकुचित करा',
relationship: { relationship: {
relationship: 'रिलेशनशिप',
primary: 'प्राथमिक टेबल', primary: 'प्राथमिक टेबल',
foreign: 'रेफरंस टेबल', foreign: 'रेफरंस टेबल',
cardinality: 'कार्डिनॅलिटी', cardinality: 'कार्डिनॅलिटी',
@@ -200,8 +198,17 @@ 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: 'हटवा',
@@ -211,8 +218,8 @@ export const mr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'कोणतेही रिलेशनशिप नाहीत', title: 'कोणत्याही डिपेंडेन्सि नाहीत',
description: 'सुरू करण्यासाठी एक रिलेशनशिप तयार करा', description: 'सुरू करण्यासाठी एक दृश्य तयार करा',
}, },
}, },
@@ -236,35 +243,6 @@ 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',
@@ -281,7 +259,6 @@ 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',
@@ -304,7 +281,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:
@@ -344,13 +321,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: 'नाव',
@@ -360,12 +337,6 @@ export const mr: LanguageTranslation = {
}, },
cancel: 'रद्द करा', cancel: 'रद्द करा',
open: 'उघडा', open: 'उघडा',
diagram_actions: {
open: 'उघडा',
duplicate: 'डुप्लिकेट',
delete: 'हटवा',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -518,11 +489,9 @@ 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: {
@@ -543,9 +512,6 @@ export const mr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा बदला', change_language: 'भाषा बदला',
}, },
on: 'चालू',
off: 'बंद',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'कार्यहरू', file: 'फाइल',
new: 'नयाँ...', new: 'नयाँ',
browse: 'ब्राउज गर्नुहोस्...', open: 'खोल्नुहोस्',
save: 'सुरक्षित गर्नुहोस्', save: 'सुरक्षित गर्नुहोस्',
import: 'डाटाबेस आयात गर्नुहोस्', import: 'डाटाबेस आयात गर्नुहोस्',
export_sql: 'SQL निर्यात गर्नुहोस्', export_sql: 'SQL निर्यात गर्नुहोस्',
export_as: 'निर्यात गर्नुहोस्', export_as: 'निर्यात गर्नुहोस्',
delete_diagram: 'हटाउनुहोस्', delete_diagram: 'डायाग्राम हटाउनुहोस्',
exit: 'बाहिर निस्कनुहोस्',
}, },
edit: { edit: {
edit: 'सम्पादन', edit: 'सम्पादन',
@@ -37,7 +29,6 @@ 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: 'डिपेन्डेन्सीहरू लुकाउनुहोस्',
@@ -75,13 +66,22 @@ 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,11 +116,14 @@ 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
@@ -146,7 +149,6 @@ export const ne: LanguageTranslation = {
field_actions: { field_actions: {
title: 'क्षेत्र विशेषताहरू', title: 'क्षेत्र विशेषताहरू',
unique: 'अनन्य', unique: 'अनन्य',
auto_increment: 'स्वचालित वृद्धि',
comments: 'टिप्पणीहरू', comments: 'टिप्पणीहरू',
no_comments: 'कुनै टिप्पणीहरू छैनन्', no_comments: 'कुनै टिप्पणीहरू छैनन्',
delete_field: 'क्षेत्र हटाउनुहोस्', delete_field: 'क्षेत्र हटाउनुहोस्',
@@ -162,7 +164,6 @@ export const ne: LanguageTranslation = {
title: 'सूचक विशेषताहरू', title: 'सूचक विशेषताहरू',
name: 'नाम', name: 'नाम',
unique: 'अनन्य', unique: 'अनन्य',
index_type: 'इन्डेक्स प्रकार',
delete_index: 'सूचक हटाउनुहोस्', delete_index: 'सूचक हटाउनुहोस्',
}, },
table_actions: { table_actions: {
@@ -179,15 +180,12 @@ export const ne: LanguageTranslation = {
description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्', description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'फिल्टर',
collapse: 'सबै लुकाउनुहोस्',
add_relationship: 'सम्बन्ध थप्नुहोस्',
relationships: 'सम्बन्धहरू', relationships: 'सम्बन्धहरू',
dependencies: 'डिपेन्डेन्सीहरू', filter: 'फिल्टर',
add_relationship: 'सम्बन्ध थप्नुहोस्',
collapse: 'सबै लुकाउनुहोस्',
relationship: { relationship: {
relationship: 'सम्बन्ध',
primary: 'मुख्य तालिका', primary: 'मुख्य तालिका',
foreign: 'परिचित तालिका', foreign: 'परिचित तालिका',
cardinality: 'कार्डिन्यालिटी', cardinality: 'कार्डिन्यालिटी',
@@ -197,8 +195,16 @@ 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: 'हटाउनुहोस्',
@@ -208,8 +214,9 @@ export const ne: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'कुनै सम्बन्धहरू छैनन्', title: 'कुनै डिपेन्डेन्सीहरू छैनन्',
description: 'सुरु गर्नका लागि एक सम्बन्ध बनाउनुहोस्', description:
'डिपेन्डेन्सीहरू देखाउनका लागि एक व्यू बनाउनुहोस्',
}, },
}, },
@@ -233,35 +240,6 @@ 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',
@@ -278,7 +256,6 @@ 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',
@@ -301,7 +278,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:
@@ -340,13 +317,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: {
@@ -357,12 +334,6 @@ export const ne: LanguageTranslation = {
}, },
cancel: 'रद्द गर्नुहोस्', cancel: 'रद्द गर्नुहोस्',
open: 'खोल्नुहोस्', open: 'खोल्नुहोस्',
diagram_actions: {
open: 'खोल्नुहोस्',
duplicate: 'डुप्लिकेट',
delete: 'मेटाउनुहोस्',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -512,11 +483,9 @@ 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: {
@@ -535,9 +504,6 @@ export const ne: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा परिवर्तन गर्नुहोस्', change_language: 'भाषा परिवर्तन गर्नुहोस्',
}, },
on: 'सक्रिय',
off: 'निष्क्रिय',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Ações', file: 'Arquivo',
new: 'Novo...', new: 'Novo',
browse: 'Navegar...', open: 'Abrir',
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', delete_diagram: 'Excluir Diagrama',
exit: 'Sair',
}, },
edit: { edit: {
edit: 'Editar', edit: 'Editar',
@@ -37,7 +29,6 @@ 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',
@@ -75,13 +66,22 @@ export const pt_BR: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Organizar Diagrama Automaticamente', title: 'Reordenar Diagrama',
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: 'Organizar Automaticamente', reorder: 'Reordenar',
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,11 +116,14 @@ 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
@@ -146,7 +149,6 @@ 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',
@@ -162,7 +164,6 @@ 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: {
@@ -179,15 +180,12 @@ export const pt_BR: LanguageTranslation = {
description: 'Crie uma tabela para começar', description: 'Crie uma tabela para começar',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Filtrar',
collapse: 'Colapsar Todas',
add_relationship: 'Adicionar Relacionamento',
relationships: 'Relacionamentos', relationships: 'Relacionamentos',
dependencies: 'Dependências', filter: 'Filtrar',
add_relationship: 'Adicionar Relacionamento',
collapse: 'Colapsar Todas',
relationship: { relationship: {
relationship: 'Relacionamento',
primary: 'Tabela Primária', primary: 'Tabela Primária',
foreign: 'Tabela Referenciada', foreign: 'Tabela Referenciada',
cardinality: 'Cardinalidade', cardinality: 'Cardinalidade',
@@ -197,8 +195,16 @@ 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',
@@ -208,8 +214,8 @@ export const pt_BR: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Sem relacionamentos', title: 'Sem dependências',
description: 'Crie um relacionamento para começar', description: 'Crie uma visualização para começar',
}, },
}, },
@@ -233,35 +239,6 @@ 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',
@@ -278,7 +255,6 @@ 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',
@@ -301,7 +277,7 @@ export const pt_BR: LanguageTranslation = {
show_all: 'Mostrar Tudo', show_all: 'Mostrar Tudo',
undo: 'Desfazer', undo: 'Desfazer',
redo: 'Refazer', redo: 'Refazer',
reorder_diagram: 'Organizar Diagrama Automaticamente', reorder_diagram: 'Reordenar Diagrama',
// 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:
@@ -340,13 +316,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: 'Banco de dados vazio', empty_diagram: 'Diagrama vazio',
continue: 'Continuar', continue: 'Continuar',
import: 'Importar', import: 'Importar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Abrir Banco de Dados', title: 'Abrir Diagrama',
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',
@@ -356,12 +332,6 @@ 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: {
@@ -511,11 +481,9 @@ 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: {
@@ -535,9 +503,6 @@ export const pt_BR: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Idioma', change_language: 'Idioma',
}, },
on: 'Ligado',
off: 'Desligado',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Действия', file: 'Файл',
new: 'Новая...', new: 'Создать',
browse: 'Обзор...', open: 'Открыть',
save: 'Сохранить', save: 'Сохранить',
import: 'Импортировать базу данных', import: 'Импортировать базу данных',
export_sql: 'Экспорт SQL', export_sql: 'Экспорт SQL',
export_as: 'Экспортировать как', export_as: 'Экспортировать как',
delete_diagram: 'Удалить', delete_diagram: 'Удалить диаграмму',
exit: 'Выход',
}, },
edit: { edit: {
edit: 'Изменение', edit: 'Изменение',
@@ -37,7 +29,6 @@ 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: 'Скрыть зависимости',
@@ -73,13 +64,22 @@ 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,11 +113,14 @@ 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: 'Очистить фильтр',
@@ -143,7 +146,6 @@ export const ru: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Атрибуты поля', title: 'Атрибуты поля',
unique: 'Уникальный', unique: 'Уникальный',
auto_increment: 'Автоинкремент',
comments: 'Комментарии', comments: 'Комментарии',
no_comments: 'Нет комментария', no_comments: 'Нет комментария',
delete_field: 'Удалить поле', delete_field: 'Удалить поле',
@@ -158,7 +160,6 @@ export const ru: LanguageTranslation = {
title: 'Атрибуты индекса', title: 'Атрибуты индекса',
name: 'Имя', name: 'Имя',
unique: 'Уникальный', unique: 'Уникальный',
index_type: 'Тип индекса',
delete_index: 'Удалить индекс', delete_index: 'Удалить индекс',
}, },
table_actions: { table_actions: {
@@ -175,15 +176,12 @@ export const ru: LanguageTranslation = {
description: 'Создайте таблицу, чтобы начать', description: 'Создайте таблицу, чтобы начать',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Ссылки',
filter: 'Фильтр',
collapse: 'Свернуть все',
add_relationship: 'Добавить отношение',
relationships: 'Отношения', relationships: 'Отношения',
dependencies: 'Зависимости', filter: 'Фильтр',
add_relationship: 'Добавить отношение',
collapse: 'Свернуть все',
relationship: { relationship: {
relationship: 'Отношение',
primary: 'Основная таблица', primary: 'Основная таблица',
foreign: 'Справочная таблица', foreign: 'Справочная таблица',
cardinality: 'Тип множественной связи', cardinality: 'Тип множественной связи',
@@ -193,10 +191,18 @@ export const ru: 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: 'Действия',
@@ -204,8 +210,8 @@ export const ru: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Нет отношений', title: 'Нет зависимостей',
description: 'Создайте отношение, чтобы начать', description: 'Создайте представление, чтобы начать',
}, },
}, },
@@ -230,35 +236,6 @@ 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',
@@ -275,7 +252,6 @@ 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',
@@ -298,7 +274,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:
@@ -336,13 +312,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: {
@@ -353,12 +329,6 @@ export const ru: LanguageTranslation = {
}, },
cancel: 'Отмена', cancel: 'Отмена',
open: 'Открыть', open: 'Открыть',
diagram_actions: {
open: 'Открыть',
duplicate: 'Дублировать',
delete: 'Удалить',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -507,10 +477,8 @@ 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: {
@@ -530,9 +498,6 @@ export const ru: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Сменить язык', change_language: 'Сменить язык',
}, },
on: 'Вкл',
off: 'Выкл',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'చర్యలు', file: 'ఫైల్',
new: 'కొత్తది...', new: 'కొత్తది',
browse: 'బ్రాఉజ్ చేయండి...', open: 'తెరవు',
save: 'సేవ్', save: 'సేవ్',
import: 'డేటాబేస్‌ను దిగుమతి చేసుకోండి', import: 'డేటాబేస్‌ను దిగుమతి చేసుకోండి',
export_sql: 'SQL ఎగుమతి', export_sql: 'SQL ఎగుమతి',
export_as: 'వగా ఎగుమతి చేయండి', export_as: 'వగా ఎగుమతి చేయండి',
delete_diagram: 'తొలగించండి', delete_diagram: 'చిత్రాన్ని తొలగించండి',
exit: 'నిష్క్రమించు',
}, },
edit: { edit: {
edit: 'సవరించు', edit: 'సవరించు',
@@ -37,7 +29,6 @@ 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: 'ఆధారాలను దాచండి',
@@ -75,13 +66,22 @@ 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,11 +116,14 @@ 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
@@ -146,7 +149,6 @@ export const te: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ఫీల్డ్ గుణాలు', title: 'ఫీల్డ్ గుణాలు',
unique: 'అద్వితీయ', unique: 'అద్వితీయ',
auto_increment: 'ఆటో ఇంక్రిమెంట్',
comments: 'వ్యాఖ్యలు', comments: 'వ్యాఖ్యలు',
no_comments: 'వ్యాఖ్యలు లేవు', no_comments: 'వ్యాఖ్యలు లేవు',
delete_field: 'ఫీల్డ్ తొలగించు', delete_field: 'ఫీల్డ్ తొలగించు',
@@ -162,7 +164,6 @@ export const te: LanguageTranslation = {
title: 'ఇండెక్స్ గుణాలు', title: 'ఇండెక్స్ గుణాలు',
name: 'పేరు', name: 'పేరు',
unique: 'అద్వితీయ', unique: 'అద్వితీయ',
index_type: 'ఇండెక్స్ రకం',
delete_index: 'ఇండెక్స్ తొలగించు', delete_index: 'ఇండెక్స్ తొలగించు',
}, },
table_actions: { table_actions: {
@@ -180,15 +181,12 @@ export const te: LanguageTranslation = {
description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి', description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'ఫిల్టర్',
collapse: 'అన్ని కూల్ చేయి',
add_relationship: 'సంబంధం జోడించు',
relationships: 'సంబంధాలు', relationships: 'సంబంధాలు',
dependencies: 'ఆధారాలు', filter: 'ఫిల్టర్',
add_relationship: 'సంబంధం జోడించు',
collapse: 'అన్ని కూల్ చేయి',
relationship: { relationship: {
relationship: 'సంబంధం',
primary: 'ప్రాథమిక పట్టిక', primary: 'ప్రాథమిక పట్టిక',
foreign: 'సూచించబడిన పట్టిక', foreign: 'సూచించబడిన పట్టిక',
cardinality: 'కార్డినాలిటీ', cardinality: 'కార్డినాలిటీ',
@@ -198,8 +196,16 @@ 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: 'ఆధారాన్ని తొలగించు',
@@ -209,8 +215,8 @@ export const te: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'సంబంధాలు లేవు', title: 'ఆధారాలు లేవు',
description: 'ప్రారంభించడానికి ఒక సంబంధం సృష్టించండి', description: 'ప్రారంభించడానికి ఒక వీక్షణ సృష్టించండి',
}, },
}, },
@@ -234,35 +240,6 @@ 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',
@@ -279,7 +256,6 @@ 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',
@@ -302,7 +278,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:
@@ -341,13 +317,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: 'పేరు',
@@ -357,12 +333,6 @@ export const te: LanguageTranslation = {
}, },
cancel: 'రద్దు', cancel: 'రద్దు',
open: 'తెరవు', open: 'తెరవు',
diagram_actions: {
open: 'తెరవు',
duplicate: 'నకలు',
delete: 'తొలగించు',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -515,11 +485,9 @@ 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: {
@@ -540,9 +508,6 @@ export const te: LanguageTranslation = {
language_select: { language_select: {
change_language: 'భాష మార్చు', change_language: 'భాష మార్చు',
}, },
on: 'ఆన్',
off: 'ఆఫ్',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Eylemler', file: 'Dosya',
new: 'Yeni...', new: 'Yeni',
browse: 'Gözat...', open: '',
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: 'Sil', delete_diagram: 'Diyagramı Sil',
exit: ıkış',
}, },
edit: { edit: {
edit: 'Düzenle', edit: 'Düzenle',
@@ -37,7 +29,6 @@ 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',
@@ -75,13 +66,22 @@ export const tr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Diyagramı Otomatik Düzenle', title: 'Diyagramı Yeniden Sırala',
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: 'Otomatik Düzenle', reorder: 'Yeniden Sırala',
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,11 +115,14 @@ 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
@@ -145,7 +148,6 @@ 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',
@@ -161,7 +163,6 @@ 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: {
@@ -179,15 +180,12 @@ export const tr: LanguageTranslation = {
description: 'Başlamak için bir tablo oluşturun', description: 'Başlamak için bir tablo oluşturun',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Filtrele',
collapse: 'Hepsini Daralt',
add_relationship: 'İlişki Ekle',
relationships: 'İlişkiler', relationships: 'İlişkiler',
dependencies: 'Bağımlılıklar', filter: 'Filtrele',
add_relationship: 'İlişki Ekle',
collapse: 'Hepsini Daralt',
relationship: { relationship: {
relationship: 'İlişki',
primary: 'Birincil Tablo', primary: 'Birincil Tablo',
foreign: 'Referans Tablo', foreign: 'Referans Tablo',
cardinality: 'Kardinalite', cardinality: 'Kardinalite',
@@ -197,8 +195,16 @@ 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',
@@ -208,8 +214,8 @@ export const tr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'İlişki yok', title: 'Bağımlılık yok',
description: 'Başlamak için bir ilişki oluşturun', description: 'Başlamak için bir görünüm oluşturun',
}, },
}, },
@@ -233,35 +239,6 @@ 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',
@@ -278,7 +255,6 @@ 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',
@@ -300,7 +276,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ı Otomatik Düzenle', reorder_diagram: 'Diyagramı Yeniden Sırala',
// 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:
@@ -337,12 +313,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ş veritabanı', empty_diagram: 'Boş diyagram',
continue: 'Devam', continue: 'Devam',
import: 'İçe Aktar', import: 'İçe Aktar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Veritabanı Aç', title: 'Diyagramı 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',
@@ -352,12 +328,6 @@ 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: {
@@ -500,11 +470,9 @@ 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',
@@ -524,9 +492,6 @@ export const tr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Dil', change_language: 'Dil',
}, },
on: 'Açık',
off: 'Kapalı',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Дії', file: 'Файл',
new: 'Нова...', new: 'Новий',
browse: 'Огляд...', open: 'Відкрити',
save: 'Зберегти', save: 'Зберегти',
import: 'Імпорт бази даних', import: 'Імпорт бази даних',
export_sql: 'Експорт SQL', export_sql: 'Експорт SQL',
export_as: 'Експортувати як', export_as: 'Експортувати як',
delete_diagram: 'Видалити', delete_diagram: 'Видалити діаграму',
exit: 'Вийти',
}, },
edit: { edit: {
edit: 'Редагувати', edit: 'Редагувати',
@@ -37,7 +29,6 @@ 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: 'Приховати залежності',
@@ -73,13 +64,22 @@ 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,11 +114,14 @@ 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
@@ -144,7 +147,6 @@ export const uk: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Атрибути полів', title: 'Атрибути полів',
unique: 'Унікальне', unique: 'Унікальне',
auto_increment: 'Автоінкремент',
comments: 'Коментарі', comments: 'Коментарі',
no_comments: 'Немає коментарів', no_comments: 'Немає коментарів',
delete_field: 'Видалити поле', delete_field: 'Видалити поле',
@@ -160,7 +162,6 @@ export const uk: LanguageTranslation = {
title: 'Атрибути індексу', title: 'Атрибути індексу',
name: 'Назва індекса', name: 'Назва індекса',
unique: 'Унікальний', unique: 'Унікальний',
index_type: 'Тип індексу',
delete_index: 'Видалити індекс', delete_index: 'Видалити індекс',
}, },
table_actions: { table_actions: {
@@ -177,15 +178,12 @@ export const uk: LanguageTranslation = {
description: 'Щоб почати, створіть таблицю', description: 'Щоб почати, створіть таблицю',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Фільтр',
collapse: 'Згорнути все',
add_relationship: 'Додати звʼязок',
relationships: 'Звʼязки', relationships: 'Звʼязки',
dependencies: 'Залежності', filter: 'Фільтр',
add_relationship: 'Додати звʼязок',
collapse: 'Згорнути все',
relationship: { relationship: {
relationship: 'Звʼязок',
primary: 'Первинна таблиця', primary: 'Первинна таблиця',
foreign: 'Посилання на таблицю', foreign: 'Посилання на таблицю',
cardinality: 'Звʼязок', cardinality: 'Звʼязок',
@@ -195,8 +193,16 @@ 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: 'Видалити',
@@ -206,8 +212,8 @@ export const uk: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Жодних зв’язків', title: 'Жодних залежностей',
description: 'Створіть зв’язок, щоб почати', description: 'Створіть подання, щоб почати',
}, },
}, },
@@ -231,35 +237,6 @@ 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',
@@ -276,7 +253,6 @@ 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',
@@ -299,7 +275,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:
@@ -337,13 +313,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: {
@@ -354,12 +330,6 @@ export const uk: LanguageTranslation = {
}, },
cancel: 'Скасувати', cancel: 'Скасувати',
open: 'Відкрити', open: 'Відкрити',
diagram_actions: {
open: 'Відкрити',
duplicate: 'Дублювати',
delete: 'Видалити',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -506,11 +476,9 @@ 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: {
@@ -529,9 +497,6 @@ export const uk: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Мова', change_language: 'Мова',
}, },
on: 'Увімк',
off: 'Вимк',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: 'Hành động', file: 'Tệp',
new: 'Mới...', new: 'Tạo mới',
browse: 'Duyệt...', open: 'Mở',
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', delete_diagram: 'Xóa sơ đồ',
exit: 'Thoát',
}, },
edit: { edit: {
edit: 'Sửa', edit: 'Sửa',
@@ -37,7 +29,6 @@ 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',
@@ -74,13 +65,22 @@ export const vi: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Tự động sắp xếp sơ đồ', title: 'Sắp xếp lại 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: 'Tự động sắp xếp', reorder: '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,11 +115,14 @@ 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
@@ -145,7 +148,6 @@ 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',
@@ -161,7 +163,6 @@ 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: {
@@ -178,15 +179,12 @@ export const vi: LanguageTranslation = {
description: 'Tạo một bảng để bắt đầu', description: 'Tạo một bảng để bắt đầu',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: 'Lọc',
collapse: 'Thu gọn tất cả',
add_relationship: 'Thêm quan hệ',
relationships: 'Quan hệ', relationships: 'Quan hệ',
dependencies: 'Phụ thuộc', filter: 'Lọc',
add_relationship: 'Thêm quan hệ',
collapse: 'Thu gọn tất 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ệ',
@@ -196,8 +194,16 @@ 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',
@@ -207,8 +213,8 @@ export const vi: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Không có quan hệ', title: 'Không có phụ thuộc',
description: 'Tạo một quan hệ để bắt đầu', description: 'Tạo bảng xem phụ thuộc để bắt đầu',
}, },
}, },
@@ -232,35 +238,6 @@ 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',
@@ -277,7 +254,6 @@ 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',
@@ -300,7 +276,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: 'Tự động sắp xếp sơ đồ', reorder_diagram: 'Sắp xếp lại 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:
@@ -338,13 +314,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: 'Cơ sở dữ liệu trống', empty_diagram: 'Sơ đồ 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ở cơ sở dữ liệu', title: 'Mở sơ đồ',
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',
@@ -354,12 +330,6 @@ 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: {
@@ -507,11 +477,9 @@ 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: {
@@ -530,9 +498,6 @@ 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,25 +2,17 @@ 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: {
actions: { file: {
actions: '操作', file: '文件',
new: '新建...', new: '新建',
browse: '浏览...', open: '打开',
save: '保存', save: '保存',
import: '导入数据库', import: '导入数据库',
export_sql: '导出 SQL 语句', export_sql: '导出 SQL 语句',
export_as: '导出为', export_as: '导出为',
delete_diagram: '删除', delete_diagram: '删除关系图',
exit: '退出',
}, },
edit: { edit: {
edit: '编辑', edit: '编辑',
@@ -37,7 +29,6 @@ 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: '隐藏依赖',
@@ -72,12 +63,21 @@ 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,11 +112,14 @@ 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
@@ -142,7 +145,6 @@ 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: '删除字段',
@@ -158,7 +160,6 @@ export const zh_CN: LanguageTranslation = {
title: '索引属性', title: '索引属性',
name: '名称', name: '名称',
unique: '唯一', unique: '唯一',
index_type: '索引类型',
delete_index: '删除索引', delete_index: '删除索引',
}, },
table_actions: { table_actions: {
@@ -175,15 +176,12 @@ export const zh_CN: LanguageTranslation = {
description: '新建表以开始', description: '新建表以开始',
}, },
}, },
refs_section: { relationships_section: {
refs: '引用',
filter: '筛选',
collapse: '全部折叠',
add_relationship: '添加关系',
relationships: '关系', relationships: '关系',
dependencies: '依赖关系', filter: '筛选',
add_relationship: '添加关系',
collapse: '全部折叠',
relationship: { relationship: {
relationship: '关系',
primary: '主表', primary: '主表',
foreign: '被引用表', foreign: '被引用表',
cardinality: '基数', cardinality: '基数',
@@ -193,8 +191,16 @@ 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: '删除',
@@ -204,8 +210,8 @@ export const zh_CN: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '无关系', title: '无依赖',
description: '创建关系以开始', description: '创建视图以开始',
}, },
}, },
@@ -229,34 +235,6 @@ 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',
@@ -273,7 +251,6 @@ 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',
@@ -296,7 +273,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:
@@ -334,13 +311,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: '名称',
@@ -350,12 +327,6 @@ export const zh_CN: LanguageTranslation = {
}, },
cancel: '取消', cancel: '取消',
open: '打开', open: '打开',
diagram_actions: {
open: '打开',
duplicate: '复制',
delete: '删除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -501,11 +472,9 @@ 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: {
@@ -524,9 +493,6 @@ export const zh_CN: LanguageTranslation = {
language_select: { language_select: {
change_language: '语言', change_language: '语言',
}, },
on: '开启',
off: '关闭',
}, },
}; };

View File

@@ -2,25 +2,17 @@ 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: {
actions: { file: {
actions: '操作', file: '檔案',
new: '新增...', new: '新增',
browse: '瀏覽...', open: '開啟',
save: '儲存', save: '儲存',
import: '匯入資料庫', import: '匯入資料庫',
export_sql: '匯出 SQL', export_sql: '匯出 SQL',
export_as: '匯出為特定格式', export_as: '匯出為特定格式',
delete_diagram: '刪除', delete_diagram: '刪除圖表',
exit: '退出',
}, },
edit: { edit: {
edit: '編輯', edit: '編輯',
@@ -37,7 +29,6 @@ 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: '隱藏相依性',
@@ -72,12 +63,21 @@ 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,11 +112,14 @@ 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
@@ -142,7 +145,6 @@ 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: '刪除欄位',
@@ -158,7 +160,6 @@ export const zh_TW: LanguageTranslation = {
title: '索引屬性', title: '索引屬性',
name: '名稱', name: '名稱',
unique: '唯一', unique: '唯一',
index_type: '索引類型',
delete_index: '刪除索引', delete_index: '刪除索引',
}, },
table_actions: { table_actions: {
@@ -175,15 +176,12 @@ export const zh_TW: LanguageTranslation = {
description: '請新增表格以開始', description: '請新增表格以開始',
}, },
}, },
refs_section: { relationships_section: {
refs: 'Refs',
filter: '篩選',
collapse: '全部摺疊',
add_relationship: '新增關聯',
relationships: '關聯', relationships: '關聯',
dependencies: '相依性', filter: '篩選',
add_relationship: '新增關聯',
collapse: '全部摺疊',
relationship: { relationship: {
relationship: '關聯',
primary: '主表格', primary: '主表格',
foreign: '參照表格', foreign: '參照表格',
cardinality: '基數', cardinality: '基數',
@@ -193,8 +191,16 @@ 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: '刪除',
@@ -204,8 +210,8 @@ export const zh_TW: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '尚無關聯', title: '尚無相依性',
description: '請建立關聯以開始', description: '請建立檢視以開始',
}, },
}, },
@@ -229,34 +235,6 @@ 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',
@@ -273,7 +251,6 @@ 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',
@@ -296,7 +273,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:
@@ -333,13 +310,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: '名稱',
@@ -349,12 +326,6 @@ export const zh_TW: LanguageTranslation = {
}, },
cancel: '取消', cancel: '取消',
open: '開啟', open: '開啟',
diagram_actions: {
open: '開啟',
duplicate: '複製',
delete: '刪除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -501,11 +472,9 @@ 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: {
@@ -524,9 +493,6 @@ export const zh_TW: LanguageTranslation = {
language_select: { language_select: {
change_language: '變更語言', change_language: '變更語言',
}, },
on: '開啟',
off: '關閉',
}, },
}; };

View File

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

View File

@@ -6,7 +6,6 @@ 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 = (
@@ -50,10 +49,6 @@ 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());
}); });
@@ -223,21 +218,6 @@ 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) => {
@@ -262,7 +242,6 @@ 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,5 +19,3 @@ 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,6 +129,9 @@ 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,6 +1,5 @@
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';
@@ -147,53 +146,3 @@ 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,7 +12,6 @@ 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
@@ -43,7 +42,6 @@ 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
{ {
@@ -97,6 +95,7 @@ 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,8 +50,5 @@ 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

@@ -1,10 +1,20 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect, vi } from 'vitest';
import { exportBaseSQL } from '../export-sql-script'; import { exportBaseSQL } from '../export-sql-script';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import type { DBTable } from '@/lib/domain/db-table'; import type { DBTable } from '@/lib/domain/db-table';
import type { DBField } from '@/lib/domain/db-field'; import type { DBField } from '@/lib/domain/db-field';
// Mock the dbml/core importer
vi.mock('@dbml/core', () => ({
importer: {
import: vi.fn((sql: string) => {
// Return a simplified DBML for testing
return sql;
}),
},
}));
describe('DBML Export - SQL Generation Tests', () => { describe('DBML Export - SQL Generation Tests', () => {
// Helper to generate test IDs and timestamps // Helper to generate test IDs and timestamps
let idCounter = 0; let idCounter = 0;
@@ -106,7 +116,7 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should contain composite primary key syntax // Should contain composite primary key syntax
expect(sql).toContain('PRIMARY KEY ("spell_id", "component_id")'); expect(sql).toContain('PRIMARY KEY (spell_id, component_id)');
// Should NOT contain individual PRIMARY KEY constraints // Should NOT contain individual PRIMARY KEY constraints
expect(sql).not.toMatch(/spell_id\s+uuid\s+NOT NULL\s+PRIMARY KEY/); expect(sql).not.toMatch(/spell_id\s+uuid\s+NOT NULL\s+PRIMARY KEY/);
expect(sql).not.toMatch( expect(sql).not.toMatch(
@@ -114,96 +124,6 @@ describe('DBML Export - SQL Generation Tests', () => {
); );
}); });
it('should not create duplicate index for composite primary key', () => {
const tableId = testId();
const field1Id = testId();
const field2Id = testId();
const field3Id = testId();
const diagram: Diagram = createDiagram({
id: testId(),
name: 'Landlord System',
databaseType: DatabaseType.POSTGRESQL,
tables: [
createTable({
id: tableId,
name: 'users_master_table',
schema: 'landlord',
fields: [
createField({
id: field1Id,
name: 'master_user_id',
type: { id: 'bigint', name: 'bigint' },
primaryKey: true,
nullable: false,
unique: false,
}),
createField({
id: field2Id,
name: 'tenant_id',
type: { id: 'bigint', name: 'bigint' },
primaryKey: true,
nullable: false,
unique: false,
}),
createField({
id: field3Id,
name: 'tenant_user_id',
type: { id: 'bigint', name: 'bigint' },
primaryKey: true,
nullable: false,
unique: false,
}),
createField({
id: testId(),
name: 'enabled',
type: { id: 'boolean', name: 'boolean' },
primaryKey: false,
nullable: true,
unique: false,
}),
],
indexes: [
{
id: testId(),
name: 'idx_users_master_table_master_user_id_tenant_id_tenant_user_id',
unique: false,
fieldIds: [field1Id, field2Id, field3Id],
createdAt: testTime,
},
{
id: testId(),
name: 'index_1',
unique: true,
fieldIds: [field2Id, field3Id],
createdAt: testTime,
},
],
}),
],
relationships: [],
});
const sql = exportBaseSQL({
diagram,
targetDatabaseType: DatabaseType.POSTGRESQL,
isDBMLFlow: true,
});
// Should contain composite primary key constraint
expect(sql).toContain(
'PRIMARY KEY ("master_user_id", "tenant_id", "tenant_user_id")'
);
// Should NOT contain the duplicate index for the primary key fields
expect(sql).not.toContain(
'CREATE INDEX idx_users_master_table_master_user_id_tenant_id_tenant_user_id'
);
// Should still contain the unique index on subset of fields
expect(sql).toContain('CREATE UNIQUE INDEX index_1');
});
it('should handle single primary keys inline', () => { it('should handle single primary keys inline', () => {
const diagram: Diagram = createDiagram({ const diagram: Diagram = createDiagram({
id: testId(), id: testId(),
@@ -245,7 +165,7 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should contain inline PRIMARY KEY // Should contain inline PRIMARY KEY
expect(sql).toMatch(/"id"\s+uuid\s+NOT NULL\s+PRIMARY KEY/); expect(sql).toMatch(/id\s+uuid\s+NOT NULL\s+PRIMARY KEY/);
// Should NOT contain separate PRIMARY KEY constraint // Should NOT contain separate PRIMARY KEY constraint
expect(sql).not.toContain('PRIMARY KEY (id)'); expect(sql).not.toContain('PRIMARY KEY (id)');
}); });
@@ -306,8 +226,8 @@ describe('DBML Export - SQL Generation Tests', () => {
expect(sql).not.toContain('DEFAULT has default'); expect(sql).not.toContain('DEFAULT has default');
expect(sql).not.toContain('DEFAULT DEFAULT has default'); expect(sql).not.toContain('DEFAULT DEFAULT has default');
// The fields should still be in the table // The fields should still be in the table
expect(sql).toContain('"is_active" boolean'); expect(sql).toContain('is_active boolean');
expect(sql).toContain('"stock_count" integer NOT NULL'); // integer gets simplified to int expect(sql).toContain('stock_count int NOT NULL'); // integer gets simplified to int
}); });
it('should handle valid default values correctly', () => { it('should handle valid default values correctly', () => {
@@ -429,8 +349,8 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should convert NOW to NOW() and ('now') to now() // Should convert NOW to NOW() and ('now') to now()
expect(sql).toContain('"created_at" timestamp DEFAULT NOW'); expect(sql).toContain('created_at timestamp DEFAULT NOW');
expect(sql).toContain('"updated_at" timestamp DEFAULT now()'); expect(sql).toContain('updated_at timestamp DEFAULT now()');
}); });
}); });
@@ -485,9 +405,9 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should handle char with explicit length // Should handle char with explicit length
expect(sql).toContain('"element_code" char(2)'); expect(sql).toContain('element_code char(2)');
// Should add default length for char without length // Should add default length for char without length
expect(sql).toContain('"status" char(1)'); expect(sql).toContain('status char(1)');
}); });
it('should not have spaces between char and parentheses', () => { it('should not have spaces between char and parentheses', () => {
@@ -596,7 +516,7 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should create a valid table without primary key // Should create a valid table without primary key
expect(sql).toContain('CREATE TABLE "experiment_logs"'); expect(sql).toContain('CREATE TABLE experiment_logs');
expect(sql).not.toContain('PRIMARY KEY'); expect(sql).not.toContain('PRIMARY KEY');
}); });
@@ -711,11 +631,11 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should create both tables // Should create both tables
expect(sql).toContain('CREATE TABLE "guilds"'); expect(sql).toContain('CREATE TABLE guilds');
expect(sql).toContain('CREATE TABLE "guild_members"'); expect(sql).toContain('CREATE TABLE guild_members');
// Should create foreign key // Should create foreign key
expect(sql).toContain( expect(sql).toContain(
'ALTER TABLE "guild_members" ADD CONSTRAINT fk_guild_members_guild FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id");' 'ALTER TABLE guild_members ADD CONSTRAINT fk_guild_members_guild FOREIGN KEY (guild_id) REFERENCES guilds (id)'
); );
}); });
}); });
@@ -789,9 +709,12 @@ describe('DBML Export - SQL Generation Tests', () => {
isDBMLFlow: true, isDBMLFlow: true,
}); });
// Should create schemas
expect(sql).toContain('CREATE SCHEMA IF NOT EXISTS transportation');
expect(sql).toContain('CREATE SCHEMA IF NOT EXISTS magic');
// Should use schema-qualified table names // Should use schema-qualified table names
expect(sql).toContain('CREATE TABLE "transportation"."portals"'); expect(sql).toContain('CREATE TABLE transportation.portals');
expect(sql).toContain('CREATE TABLE "magic"."spells"'); expect(sql).toContain('CREATE TABLE magic.spells');
}); });
}); });
@@ -838,7 +761,7 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should still create table structure // Should still create table structure
expect(sql).toContain('CREATE TABLE "empty_table"'); expect(sql).toContain('CREATE TABLE empty_table');
expect(sql).toContain('(\n\n)'); expect(sql).toContain('(\n\n)');
}); });
@@ -939,9 +862,9 @@ describe('DBML Export - SQL Generation Tests', () => {
}); });
// Should include precision and scale // Should include precision and scale
expect(sql).toContain('"amount" numeric(15, 2)'); expect(sql).toContain('amount numeric(15, 2)');
// Should include precision only when scale is not provided // Should include precision only when scale is not provided
expect(sql).toContain('"interest_rate" numeric(5)'); expect(sql).toContain('interest_rate numeric(5)');
}); });
}); });
}); });

View File

@@ -156,11 +156,11 @@ export function exportMSSQL({
const notNull = field.nullable ? '' : ' NOT NULL'; const notNull = field.nullable ? '' : ' NOT NULL';
// Check if identity column // Check if identity column
const identity = const identity = field.default
field.increment || ?.toLowerCase()
field.default?.toLowerCase().includes('identity') .includes('identity')
? ' IDENTITY(1,1)' ? ' IDENTITY(1,1)'
: ''; : '';
const unique = const unique =
!field.primaryKey && field.unique ? ' UNIQUE' : ''; !field.primaryKey && field.unique ? ' UNIQUE' : '';
@@ -168,7 +168,6 @@ export function exportMSSQL({
// Handle default value using SQL Server specific parser // Handle default value using SQL Server specific parser
const defaultValue = const defaultValue =
field.default && field.default &&
!field.increment &&
!field.default.toLowerCase().includes('identity') !field.default.toLowerCase().includes('identity')
? ` DEFAULT ${parseMSSQLDefault(field)}` ? ` DEFAULT ${parseMSSQLDefault(field)}`
: ''; : '';
@@ -178,15 +177,7 @@ export function exportMSSQL({
}) })
.join(',\n')}${ .join(',\n')}${
table.fields.filter((f) => f.primaryKey).length > 0 table.fields.filter((f) => f.primaryKey).length > 0
? `,\n ${(() => { ? `,\n PRIMARY KEY (${table.fields
// Find PK index to get the constraint name
const pkIndex = table.indexes.find(
(idx) => idx.isPrimaryKey
);
return pkIndex?.name
? `CONSTRAINT [${pkIndex.name}] `
: '';
})()}PRIMARY KEY (${table.fields
.filter((f) => f.primaryKey) .filter((f) => f.primaryKey)
.map((f) => `[${f.name}]`) .map((f) => `[${f.name}]`)
.join(', ')})` .join(', ')})`

View File

@@ -274,15 +274,14 @@ export function exportMySQL({
// Handle auto_increment - MySQL uses AUTO_INCREMENT keyword // Handle auto_increment - MySQL uses AUTO_INCREMENT keyword
let autoIncrement = ''; let autoIncrement = '';
if ( if (
field.increment || field.primaryKey &&
(field.primaryKey && (field.default
(field.default ?.toLowerCase()
.includes('identity') ||
field.default
?.toLowerCase() ?.toLowerCase()
.includes('identity') || .includes('autoincrement') ||
field.default field.default?.includes('nextval'))
?.toLowerCase()
.includes('autoincrement') ||
field.default?.includes('nextval')))
) { ) {
autoIncrement = ' AUTO_INCREMENT'; autoIncrement = ' AUTO_INCREMENT';
} }
@@ -291,10 +290,9 @@ export function exportMySQL({
const unique = const unique =
!field.primaryKey && field.unique ? ' UNIQUE' : ''; !field.primaryKey && field.unique ? ' UNIQUE' : '';
// Handle default value - skip if auto increment // Handle default value
const defaultValue = const defaultValue =
field.default && field.default &&
!field.increment &&
!field.default.toLowerCase().includes('identity') && !field.default.toLowerCase().includes('identity') &&
!field.default !field.default
.toLowerCase() .toLowerCase()
@@ -313,15 +311,7 @@ export function exportMySQL({
.join(',\n')}${ .join(',\n')}${
// Add PRIMARY KEY as table constraint // Add PRIMARY KEY as table constraint
primaryKeyFields.length > 0 primaryKeyFields.length > 0
? `,\n ${(() => { ? `,\n PRIMARY KEY (${primaryKeyFields
// Find PK index to get the constraint name
const pkIndex = table.indexes.find(
(idx) => idx.isPrimaryKey
);
return pkIndex?.name
? `CONSTRAINT \`${pkIndex.name}\` `
: '';
})()}PRIMARY KEY (${primaryKeyFields
.map((f) => `\`${f.name}\``) .map((f) => `\`${f.name}\``)
.join(', ')})` .join(', ')})`
: '' : ''

View File

@@ -286,14 +286,10 @@ export function exportPostgreSQL({
} }
} }
// Handle array types (check if isArray flag or if type name ends with '[]') // Handle array types (check if the type name ends with '[]')
if (field.isArray || typeName.endsWith('[]')) { if (typeName.endsWith('[]')) {
// Remove any existing [] notation typeWithSize =
const baseTypeWithoutArray = typeWithSize.replace( typeWithSize.replace('[]', '') + '[]';
/\[\]$/,
''
);
typeWithSize = baseTypeWithoutArray + '[]';
} }
const notNull = field.nullable ? '' : ' NOT NULL'; const notNull = field.nullable ? '' : ' NOT NULL';
@@ -329,15 +325,7 @@ export function exportPostgreSQL({
}) })
.join(',\n')}${ .join(',\n')}${
primaryKeyFields.length > 0 primaryKeyFields.length > 0
? `,\n ${(() => { ? `,\n PRIMARY KEY (${primaryKeyFields
// Find PK index to get the constraint name
const pkIndex = table.indexes.find(
(idx) => idx.isPrimaryKey
);
return pkIndex?.name
? `CONSTRAINT "${pkIndex.name}" `
: '';
})()}PRIMARY KEY (${primaryKeyFields
.map((f) => `"${f.name}"`) .map((f) => `"${f.name}"`)
.join(', ')})` .join(', ')})`
: '' : ''
@@ -417,7 +405,7 @@ export function exportPostgreSQL({
.filter(Boolean); .filter(Boolean);
return indexFieldNames.length > 0 return indexFieldNames.length > 0
? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName}${index.type && index.type !== 'btree' ? ` USING ${index.type.toUpperCase()}` : ''} (${indexFieldNames.join(', ')});` ? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName} (${indexFieldNames.join(', ')});`
: ''; : '';
}) })
.filter(Boolean); .filter(Boolean);

View File

@@ -67,9 +67,8 @@ function parseSQLiteDefault(field: DBField): string {
return `'${defaultValue.replace(/'/g, "''")}'`; return `'${defaultValue.replace(/'/g, "''")}'`;
} }
// Preserve original types for SQLite export (only map when necessary) // Map problematic types to SQLite compatible types
function mapSQLiteType(typeName: string, isPrimaryKey: boolean): string { function mapSQLiteType(typeName: string, isPrimaryKey: boolean): string {
const originalType = typeName;
typeName = typeName.toLowerCase(); typeName = typeName.toLowerCase();
// Special handling for primary key integer columns (autoincrement requires INTEGER PRIMARY KEY) // Special handling for primary key integer columns (autoincrement requires INTEGER PRIMARY KEY)
@@ -77,62 +76,59 @@ function mapSQLiteType(typeName: string, isPrimaryKey: boolean): string {
return 'INTEGER'; // Must be uppercase for SQLite to recognize it for AUTOINCREMENT return 'INTEGER'; // Must be uppercase for SQLite to recognize it for AUTOINCREMENT
} }
// Preserve original type names that SQLite accepts // Map common types to SQLite's simplified type system
switch (typeName) { switch (typeName) {
// Keep these types as-is
case 'integer':
case 'text':
case 'real':
case 'blob':
case 'numeric':
case 'decimal':
case 'boolean':
case 'date':
case 'datetime':
case 'timestamp':
case 'float':
case 'double':
case 'varchar':
case 'char':
case 'int': case 'int':
case 'smallint': case 'smallint':
case 'tinyint': case 'tinyint':
case 'mediumint':
case 'bigint': case 'bigint':
case 'json': return 'INTEGER';
return typeName.toUpperCase();
// Only map types that SQLite truly doesn't recognize case 'decimal':
case 'numeric':
case 'float':
case 'double':
case 'real':
return 'REAL';
case 'char':
case 'nchar': case 'nchar':
case 'varchar':
case 'nvarchar': case 'nvarchar':
case 'text':
case 'ntext': case 'ntext':
case 'character varying': case 'character varying':
case 'character': case 'character':
return 'TEXT'; return 'TEXT';
case 'date':
case 'datetime':
case 'timestamp':
case 'datetime2': case 'datetime2':
return 'DATETIME'; return 'TEXT'; // SQLite doesn't have dedicated date types
case 'blob':
case 'binary': case 'binary':
case 'varbinary': case 'varbinary':
case 'image': case 'image':
return 'BLOB'; return 'BLOB';
case 'bit': case 'bit':
return 'BOOLEAN'; case 'boolean':
return 'INTEGER'; // SQLite doesn't have a boolean type, use INTEGER
case 'user-defined': case 'user-defined':
case 'json':
case 'jsonb': case 'jsonb':
return 'TEXT'; return 'TEXT'; // Store as JSON text
case 'array': case 'array':
return 'TEXT'; return 'TEXT'; // Store as serialized array text
case 'geometry': case 'geometry':
case 'geography': case 'geography':
return 'BLOB'; return 'BLOB'; // Store spatial data as BLOB in SQLite
case 'mediumint':
return 'INTEGER';
} }
// If type has array notation (ends with []), treat as TEXT // If type has array notation (ends with []), treat as TEXT
@@ -140,8 +136,8 @@ function mapSQLiteType(typeName: string, isPrimaryKey: boolean): string {
return 'TEXT'; return 'TEXT';
} }
// For any other types, preserve the original // For any other types, default to TEXT
return originalType.toUpperCase(); return typeName;
} }
export function exportSQLite({ export function exportSQLite({
@@ -161,11 +157,6 @@ export function exportSQLite({
// Start SQL script - SQLite doesn't use schemas, so we skip schema creation // Start SQL script - SQLite doesn't use schemas, so we skip schema creation
let sqlScript = '-- SQLite database export\n'; let sqlScript = '-- SQLite database export\n';
// Add PRAGMA foreign_keys = ON if there are relationships
if (relationships && relationships.length > 0) {
sqlScript += 'PRAGMA foreign_keys = ON;\n\n';
}
// Begin transaction for faster import // Begin transaction for faster import
sqlScript += 'BEGIN TRANSACTION;\n'; sqlScript += 'BEGIN TRANSACTION;\n';
@@ -214,86 +205,6 @@ export function exportSQLite({
'integer' || 'integer' ||
primaryKeyFields[0].type.name.toLowerCase() === 'int'); primaryKeyFields[0].type.name.toLowerCase() === 'int');
// Collect foreign key constraints for this table
const tableForeignKeys: string[] = [];
relationships.forEach((r: DBRelationship) => {
const sourceTable = tables.find(
(t) => t.id === r.sourceTableId
);
const targetTable = tables.find(
(t) => t.id === r.targetTableId
);
if (
!sourceTable ||
!targetTable ||
sourceTable.isView ||
targetTable.isView ||
sqliteSystemTables.includes(
sourceTable.name.toLowerCase()
) ||
sqliteSystemTables.includes(
targetTable.name.toLowerCase()
)
) {
return;
}
const sourceField = sourceTable.fields.find(
(f) => f.id === r.sourceFieldId
);
const targetField = targetTable.fields.find(
(f) => f.id === r.targetFieldId
);
if (!sourceField || !targetField) {
return;
}
// Determine which table should have the foreign key based on cardinality
let fkTable, fkField, refTable, refField;
if (
r.sourceCardinality === 'one' &&
r.targetCardinality === 'many'
) {
// FK goes on target table
fkTable = targetTable;
fkField = targetField;
refTable = sourceTable;
refField = sourceField;
} else if (
r.sourceCardinality === 'many' &&
r.targetCardinality === 'one'
) {
// FK goes on source table
fkTable = sourceTable;
fkField = sourceField;
refTable = targetTable;
refField = targetField;
} else if (
r.sourceCardinality === 'one' &&
r.targetCardinality === 'one'
) {
// For 1:1, FK can go on either side, but typically goes on the table that references the other
// We'll keep the current behavior for 1:1
fkTable = sourceTable;
fkField = sourceField;
refTable = targetTable;
refField = targetField;
} else {
// Many-to-many relationships need a junction table, skip for now
return;
}
// If this foreign key belongs to the current table, add it
if (fkTable.id === table.id) {
tableForeignKeys.push(
` FOREIGN KEY("${fkField.name}") REFERENCES "${refTable.name}"("${refField.name}")`
);
}
});
return `${schemaComment}${ return `${schemaComment}${
table.comments ? formatTableComment(table.comments) : '' table.comments ? formatTableComment(table.comments) : ''
}CREATE TABLE IF NOT EXISTS ${tableName} (\n${table.fields }CREATE TABLE IF NOT EXISTS ${tableName} (\n${table.fields
@@ -301,40 +212,14 @@ export function exportSQLite({
const fieldName = `"${field.name}"`; const fieldName = `"${field.name}"`;
// Handle type name - map to SQLite compatible types // Handle type name - map to SQLite compatible types
const baseTypeName = mapSQLiteType( const typeName = mapSQLiteType(
field.type.name, field.type.name,
field.primaryKey field.primaryKey
); );
// Add size/precision/scale parameters if applicable // SQLite ignores length specifiers, so we don't add them
let typeWithParams = baseTypeName; // We'll keep this simple without size info
const typeWithoutSize = typeName;
// Add character maximum length for VARCHAR, CHAR, etc.
if (
field.characterMaximumLength &&
['VARCHAR', 'CHAR', 'TEXT'].includes(
baseTypeName.toUpperCase()
)
) {
typeWithParams = `${baseTypeName}(${field.characterMaximumLength})`;
}
// Add precision and scale for DECIMAL, NUMERIC, etc.
else if (
field.precision &&
[
'DECIMAL',
'NUMERIC',
'REAL',
'FLOAT',
'DOUBLE',
].includes(baseTypeName.toUpperCase())
) {
if (field.scale) {
typeWithParams = `${baseTypeName}(${field.precision}, ${field.scale})`;
} else {
typeWithParams = `${baseTypeName}(${field.precision})`;
}
}
const notNull = field.nullable ? '' : ' NOT NULL'; const notNull = field.nullable ? '' : ' NOT NULL';
@@ -343,10 +228,9 @@ export function exportSQLite({
if ( if (
field.primaryKey && field.primaryKey &&
singleIntegerPrimaryKey && singleIntegerPrimaryKey &&
(field.increment || (field.default
field.default ?.toLowerCase()
?.toLowerCase() .includes('identity') ||
.includes('identity') ||
field.default field.default
?.toLowerCase() ?.toLowerCase()
.includes('autoincrement') || .includes('autoincrement') ||
@@ -363,7 +247,6 @@ export function exportSQLite({
let defaultValue = ''; let defaultValue = '';
if ( if (
field.default && field.default &&
!field.increment &&
!field.default.toLowerCase().includes('identity') && !field.default.toLowerCase().includes('identity') &&
!field.default !field.default
.toLowerCase() .toLowerCase()
@@ -384,7 +267,7 @@ export function exportSQLite({
? ' PRIMARY KEY' + autoIncrement ? ' PRIMARY KEY' + autoIncrement
: ''; : '';
return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${typeWithParams}${primaryKey}${notNull}${unique}${defaultValue}`; return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${typeWithoutSize}${primaryKey}${notNull}${unique}${defaultValue}`;
}) })
.join(',\n')}${ .join(',\n')}${
// Add PRIMARY KEY as table constraint for composite primary keys or non-INTEGER primary keys // Add PRIMARY KEY as table constraint for composite primary keys or non-INTEGER primary keys
@@ -393,11 +276,6 @@ export function exportSQLite({
.map((f) => `"${f.name}"`) .map((f) => `"${f.name}"`)
.join(', ')})` .join(', ')})`
: '' : ''
}${
// Add foreign key constraints
tableForeignKeys.length > 0
? ',\n' + tableForeignKeys.join(',\n')
: ''
}\n);\n${ }\n);\n${
// Add indexes - SQLite doesn't support indexes in CREATE TABLE // Add indexes - SQLite doesn't support indexes in CREATE TABLE
(() => { (() => {
@@ -455,8 +333,82 @@ export function exportSQLite({
.filter(Boolean) // Remove empty strings (views) .filter(Boolean) // Remove empty strings (views)
.join('\n'); .join('\n');
} }
// Foreign keys are now included inline in CREATE TABLE statements // Generate table constraints and triggers for foreign keys
// No need for separate ALTER TABLE statements in SQLite // SQLite handles foreign keys differently - we'll add them with CREATE TABLE statements
// But we'll also provide individual ALTER TABLE statements as comments for reference
if (relationships.length > 0) {
sqlScript += '\n-- Foreign key constraints\n';
sqlScript +=
'-- Note: SQLite requires foreign_keys pragma to be enabled:\n';
sqlScript += '-- PRAGMA foreign_keys = ON;\n';
relationships.forEach((r: DBRelationship) => {
const sourceTable = tables.find((t) => t.id === r.sourceTableId);
const targetTable = tables.find((t) => t.id === r.targetTableId);
if (
!sourceTable ||
!targetTable ||
sourceTable.isView ||
targetTable.isView ||
sqliteSystemTables.includes(sourceTable.name.toLowerCase()) ||
sqliteSystemTables.includes(targetTable.name.toLowerCase())
) {
return;
}
const sourceField = sourceTable.fields.find(
(f) => f.id === r.sourceFieldId
);
const targetField = targetTable.fields.find(
(f) => f.id === r.targetFieldId
);
if (!sourceField || !targetField) {
return;
}
// Determine which table should have the foreign key based on cardinality
let fkTable, fkField, refTable, refField;
if (
r.sourceCardinality === 'one' &&
r.targetCardinality === 'many'
) {
// FK goes on target table
fkTable = targetTable;
fkField = targetField;
refTable = sourceTable;
refField = sourceField;
} else if (
r.sourceCardinality === 'many' &&
r.targetCardinality === 'one'
) {
// FK goes on source table
fkTable = sourceTable;
fkField = sourceField;
refTable = targetTable;
refField = targetField;
} else if (
r.sourceCardinality === 'one' &&
r.targetCardinality === 'one'
) {
// For 1:1, FK can go on either side, but typically goes on the table that references the other
// We'll keep the current behavior for 1:1
fkTable = sourceTable;
fkField = sourceField;
refTable = targetTable;
refField = targetField;
} else {
// Many-to-many relationships need a junction table, skip for now
return;
}
// Create commented out version of what would be ALTER TABLE statement
sqlScript += `-- ALTER TABLE "${fkTable.name}" ADD CONSTRAINT "fk_${fkTable.name}_${fkField.name}" FOREIGN KEY("${fkField.name}") REFERENCES "${refTable.name}"("${refField.name}");\n`;
});
}
// Commit transaction // Commit transaction
sqlScript += '\nCOMMIT;\n'; sqlScript += '\nCOMMIT;\n';

View File

@@ -2,129 +2,36 @@ import type { Diagram } from '../../domain/diagram';
import { OPENAI_API_KEY, OPENAI_API_ENDPOINT, LLM_MODEL_NAME } from '@/lib/env'; import { OPENAI_API_KEY, OPENAI_API_ENDPOINT, LLM_MODEL_NAME } from '@/lib/env';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import type { DBTable } from '@/lib/domain/db-table'; import type { DBTable } from '@/lib/domain/db-table';
import { dataTypeMap, type DataType } from '../data-types/data-types'; import type { DataType } from '../data-types/data-types';
import { generateCacheKey, getFromCache, setInCache } from './export-sql-cache'; import { generateCacheKey, getFromCache, setInCache } from './export-sql-cache';
import { exportMSSQL } from './export-per-type/mssql'; import { exportMSSQL } from './export-per-type/mssql';
import { exportPostgreSQL } from './export-per-type/postgresql'; import { exportPostgreSQL } from './export-per-type/postgresql';
import { exportSQLite } from './export-per-type/sqlite'; import { exportSQLite } from './export-per-type/sqlite';
import { exportMySQL } from './export-per-type/mysql'; import { exportMySQL } from './export-per-type/mysql';
import { escapeSQLComment } from './export-per-type/common';
import {
databaseTypesWithCommentSupport,
supportsCustomTypes,
} from '@/lib/domain/database-capabilities';
// Function to format default values with proper quoting
const formatDefaultValue = (value: string): string => {
const trimmed = value.trim();
// SQL keywords and function-like keywords that don't need quotes
const keywords = [
'TRUE',
'FALSE',
'NULL',
'CURRENT_TIMESTAMP',
'CURRENT_DATE',
'CURRENT_TIME',
'NOW',
'GETDATE',
'NEWID',
'UUID',
];
if (keywords.includes(trimmed.toUpperCase())) {
return trimmed;
}
// Function calls (contain parentheses) don't need quotes
if (trimmed.includes('(') && trimmed.includes(')')) {
return trimmed;
}
// Numbers don't need quotes
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
return trimmed;
}
// Already quoted strings - keep as is
if (
(trimmed.startsWith("'") && trimmed.endsWith("'")) ||
(trimmed.startsWith('"') && trimmed.endsWith('"'))
) {
return trimmed;
}
// Check if it's a simple identifier (alphanumeric, no spaces) that might be a currency or enum
// These typically don't have spaces and are short (< 10 chars)
if (/^[A-Z][A-Z0-9_]*$/i.test(trimmed) && trimmed.length <= 10) {
return trimmed; // Treat as unquoted identifier (e.g., EUR, USD)
}
// Everything else needs to be quoted and escaped
return `'${trimmed.replace(/'/g, "''")}'`;
};
// Function to simplify verbose data type names // Function to simplify verbose data type names
const simplifyDataType = (typeName: string): string => { const simplifyDataType = (typeName: string): string => {
const typeMap: Record<string, string> = {}; const typeMap: Record<string, string> = {
'character varying': 'varchar',
'char varying': 'varchar',
integer: 'int',
int4: 'int',
int8: 'bigint',
serial4: 'serial',
serial8: 'bigserial',
float8: 'double precision',
float4: 'real',
bool: 'boolean',
character: 'char',
'timestamp without time zone': 'timestamp',
'timestamp with time zone': 'timestamptz',
'time without time zone': 'time',
'time with time zone': 'timetz',
};
return typeMap[typeName.toLowerCase()] || typeName; return typeMap[typeName.toLowerCase()] || typeName;
}; };
// Helper function to properly quote table/schema names with special characters
const getQuotedTableName = (
table: DBTable,
isDBMLFlow: boolean = false
): string => {
// Check if a name is already quoted
const isAlreadyQuoted = (name: string) => {
return (
(name.startsWith('"') && name.endsWith('"')) ||
(name.startsWith('`') && name.endsWith('`')) ||
(name.startsWith('[') && name.endsWith(']'))
);
};
// Only add quotes if needed and not already quoted
const quoteIfNeeded = (name: string) => {
if (isAlreadyQuoted(name)) {
return name;
}
const needsQuoting = /[^a-zA-Z0-9_]/.test(name) || isDBMLFlow;
return needsQuoting ? `"${name}"` : name;
};
if (table.schema) {
const quotedSchema = quoteIfNeeded(table.schema);
const quotedTable = quoteIfNeeded(table.name);
return `${quotedSchema}.${quotedTable}`;
} else {
return quoteIfNeeded(table.name);
}
};
const getQuotedFieldName = (
fieldName: string,
isDBMLFlow: boolean = false
): string => {
// Check if a name is already quoted
const isAlreadyQuoted = (name: string) => {
return (
(name.startsWith('"') && name.endsWith('"')) ||
(name.startsWith('`') && name.endsWith('`')) ||
(name.startsWith('[') && name.endsWith(']'))
);
};
if (isAlreadyQuoted(fieldName)) {
return fieldName;
}
// For DBML flow, always quote field names
// Otherwise, only quote if it contains special characters
const needsQuoting = /[^a-zA-Z0-9_]/.test(fieldName) || isDBMLFlow;
return needsQuoting ? `"${fieldName}"` : fieldName;
};
export const exportBaseSQL = ({ export const exportBaseSQL = ({
diagram, diagram,
targetDatabaseType, targetDatabaseType,
@@ -168,21 +75,18 @@ export const exportBaseSQL = ({
let sqlScript = ''; let sqlScript = '';
// First create the CREATE SCHEMA statements for all the found schemas based on tables // First create the CREATE SCHEMA statements for all the found schemas based on tables
// Skip schema creation for DBML flow as DBML doesn't support CREATE SCHEMA syntax const schemas = new Set<string>();
if (!isDBMLFlow) { tables.forEach((table) => {
const schemas = new Set<string>(); if (table.schema) {
tables.forEach((table) => { schemas.add(table.schema);
if (table.schema) { }
schemas.add(table.schema); });
}
});
// Add CREATE SCHEMA statements if any schemas exist // Add CREATE SCHEMA statements if any schemas exist
schemas.forEach((schema) => { schemas.forEach((schema) => {
sqlScript += `CREATE SCHEMA IF NOT EXISTS "${schema}";\n`; sqlScript += `CREATE SCHEMA IF NOT EXISTS ${schema};\n`;
}); });
if (schemas.size > 0) sqlScript += '\n'; // Add newline only if schemas were added if (schemas.size > 0) sqlScript += '\n'; // Add newline only if schemas were added
}
// Add CREATE TYPE statements for ENUMs and COMPOSITE types from diagram.customTypes // Add CREATE TYPE statements for ENUMs and COMPOSITE types from diagram.customTypes
if (diagram.customTypes && diagram.customTypes.length > 0) { if (diagram.customTypes && diagram.customTypes.length > 0) {
@@ -201,7 +105,10 @@ export const exportBaseSQL = ({
// or if we rely on the DBML generator to create Enums separately (as currently done) // or if we rely on the DBML generator to create Enums separately (as currently done)
// For now, let's assume PostgreSQL-style for demonstration if isDBMLFlow is false. // For now, let's assume PostgreSQL-style for demonstration if isDBMLFlow is false.
// If isDBMLFlow is true, we let TableDBML.tsx handle Enum syntax directly. // If isDBMLFlow is true, we let TableDBML.tsx handle Enum syntax directly.
if (supportsCustomTypes(targetDatabaseType) && !isDBMLFlow) { if (
targetDatabaseType === DatabaseType.POSTGRESQL &&
!isDBMLFlow
) {
const enumValues = customType.values const enumValues = customType.values
.map((v) => `'${v.replace(/'/g, "''")}'`) .map((v) => `'${v.replace(/'/g, "''")}'`)
.join(', '); .join(', ');
@@ -214,7 +121,10 @@ export const exportBaseSQL = ({
) { ) {
// For PostgreSQL, generate CREATE TYPE ... AS (...) // For PostgreSQL, generate CREATE TYPE ... AS (...)
// This is crucial for composite types to be recognized by the DBML importer // This is crucial for composite types to be recognized by the DBML importer
if (supportsCustomTypes(targetDatabaseType) || isDBMLFlow) { if (
targetDatabaseType === DatabaseType.POSTGRESQL ||
isDBMLFlow
) {
// Assume other DBs might not support this or DBML flow needs it // Assume other DBs might not support this or DBML flow needs it
const compositeFields = customType.fields const compositeFields = customType.fields
.map((f) => `${f.field} ${simplifyDataType(f.type)}`) .map((f) => `${f.field} ${simplifyDataType(f.type)}`)
@@ -229,12 +139,13 @@ export const exportBaseSQL = ({
(ct.kind === 'enum' && (ct.kind === 'enum' &&
ct.values && ct.values &&
ct.values.length > 0 && ct.values.length > 0 &&
supportsCustomTypes(targetDatabaseType) && targetDatabaseType === DatabaseType.POSTGRESQL &&
!isDBMLFlow) || !isDBMLFlow) ||
(ct.kind === 'composite' && (ct.kind === 'composite' &&
ct.fields && ct.fields &&
ct.fields.length > 0 && ct.fields.length > 0 &&
(supportsCustomTypes(targetDatabaseType) || isDBMLFlow)) (targetDatabaseType === DatabaseType.POSTGRESQL ||
isDBMLFlow))
) )
) { ) {
sqlScript += '\n'; sqlScript += '\n';
@@ -267,7 +178,9 @@ export const exportBaseSQL = ({
// Loop through each non-view table to generate the SQL statements // Loop through each non-view table to generate the SQL statements
nonViewTables.forEach((table) => { nonViewTables.forEach((table) => {
const tableName = getQuotedTableName(table, isDBMLFlow); const tableName = table.schema
? `${table.schema}.${table.name}`
: table.name;
sqlScript += `CREATE TABLE ${tableName} (\n`; sqlScript += `CREATE TABLE ${tableName} (\n`;
// Check for composite primary keys // Check for composite primary keys
@@ -294,7 +207,7 @@ export const exportBaseSQL = ({
if ( if (
customEnumType && customEnumType &&
supportsCustomTypes(targetDatabaseType) && targetDatabaseType === DatabaseType.POSTGRESQL &&
!isDBMLFlow !isDBMLFlow
) { ) {
typeName = customEnumType.schema typeName = customEnumType.schema
@@ -336,15 +249,7 @@ export const exportBaseSQL = ({
typeName = 'char'; typeName = 'char';
} }
const quotedFieldName = getQuotedFieldName(field.name, isDBMLFlow); sqlScript += ` ${field.name} ${typeName}`;
// Quote multi-word type names for DBML flow to prevent @dbml/core parser issues
const quotedTypeName =
isDBMLFlow && typeName.includes(' ')
? `"${typeName}"`
: typeName;
sqlScript += ` ${quotedFieldName} ${quotedTypeName}`;
// Add size for character types // Add size for character types
if ( if (
@@ -364,31 +269,11 @@ export const exportBaseSQL = ({
sqlScript += `(1)`; sqlScript += `(1)`;
} }
// Add precision and scale for numeric types only // Add precision and scale for numeric types
const precisionAndScaleTypes = dataTypeMap[targetDatabaseType] if (field.precision && field.scale) {
.filter( sqlScript += `(${field.precision}, ${field.scale})`;
(t) => } else if (field.precision) {
t.fieldAttributes?.precision && t.fieldAttributes?.scale sqlScript += `(${field.precision})`;
)
.map((t) => t.name);
const isNumericType = precisionAndScaleTypes.some(
(t) =>
field.type.name.toLowerCase().includes(t) ||
typeName.toLowerCase().includes(t)
);
if (isNumericType) {
if (field.precision && field.scale) {
sqlScript += `(${field.precision}, ${field.scale})`;
} else if (field.precision) {
sqlScript += `(${field.precision})`;
}
}
// Add array suffix if field is an array (after type size and precision)
if (field.isArray) {
sqlScript += '[]';
} }
// Handle NOT NULL constraint // Handle NOT NULL constraint
@@ -401,30 +286,8 @@ export const exportBaseSQL = ({
sqlScript += ` UNIQUE`; sqlScript += ` UNIQUE`;
} }
// Handle AUTO INCREMENT
if (field.increment) {
if (isDBMLFlow) {
// For DBML flow, generate proper database-specific syntax
if (
targetDatabaseType === DatabaseType.MYSQL ||
targetDatabaseType === DatabaseType.MARIADB
) {
sqlScript += ` AUTO_INCREMENT`;
} else if (targetDatabaseType === DatabaseType.SQL_SERVER) {
sqlScript += ` IDENTITY(1,1)`;
} else if (targetDatabaseType === DatabaseType.SQLITE) {
// SQLite AUTOINCREMENT only works with INTEGER PRIMARY KEY
// Will be handled when PRIMARY KEY is added
}
// PostgreSQL/CockroachDB: increment attribute added by restoreIncrementAttribute in DBML export
} else {
// For non-DBML flow, add as a comment for AI to process
sqlScript += ` /* AUTO_INCREMENT */`;
}
}
// Handle DEFAULT value // Handle DEFAULT value
if (field.default && !field.increment) { if (field.default) {
// Temp remove default user-define value when it have it // Temp remove default user-define value when it have it
let fieldDefault = field.default; let fieldDefault = field.default;
@@ -453,127 +316,55 @@ export const exportBaseSQL = ({
fieldDefault = `now()`; fieldDefault = `now()`;
} }
// Fix CURRENT_DATE() for PostgreSQL in DBML flow - PostgreSQL uses CURRENT_DATE without parentheses sqlScript += ` DEFAULT ${fieldDefault}`;
if (
isDBMLFlow &&
targetDatabaseType === DatabaseType.POSTGRESQL
) {
if (fieldDefault.toUpperCase() === 'CURRENT_DATE()') {
fieldDefault = 'CURRENT_DATE';
}
}
// Format default value with proper quoting
const formattedDefault = formatDefaultValue(fieldDefault);
sqlScript += ` DEFAULT ${formattedDefault}`;
} }
} }
// Handle PRIMARY KEY constraint - only add inline if no PK index with custom name // Handle PRIMARY KEY constraint - only add inline if not composite
const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey); if (field.primaryKey && !hasCompositePrimaryKey) {
if (field.primaryKey && !hasCompositePrimaryKey && !pkIndex?.name) {
sqlScript += ' PRIMARY KEY'; sqlScript += ' PRIMARY KEY';
// For SQLite with DBML flow, add AUTOINCREMENT after PRIMARY KEY
if (
isDBMLFlow &&
field.increment &&
targetDatabaseType === DatabaseType.SQLITE &&
(typeName.toLowerCase() === 'integer' ||
typeName.toLowerCase() === 'int')
) {
sqlScript += ' AUTOINCREMENT';
}
} }
// Add a comma after each field except the last one (or before PK constraint) // Add a comma after each field except the last one (or before composite primary key)
const needsPKConstraint = if (index < table.fields.length - 1 || hasCompositePrimaryKey) {
hasCompositePrimaryKey ||
(primaryKeyFields.length === 1 && pkIndex?.name);
if (index < table.fields.length - 1 || needsPKConstraint) {
sqlScript += ',\n'; sqlScript += ',\n';
} }
}); });
// Add primary key constraint if needed (for composite PKs or single PK with custom name) // Add composite primary key constraint if needed
const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey); if (hasCompositePrimaryKey) {
if ( const pkFieldNames = primaryKeyFields.map((f) => f.name).join(', ');
hasCompositePrimaryKey || sqlScript += `\n PRIMARY KEY (${pkFieldNames})`;
(primaryKeyFields.length === 1 && pkIndex?.name)
) {
const pkFieldNames = primaryKeyFields
.map((f) => getQuotedFieldName(f.name, isDBMLFlow))
.join(', ');
if (pkIndex?.name) {
sqlScript += `\n CONSTRAINT ${pkIndex.name} PRIMARY KEY (${pkFieldNames})`;
} else {
sqlScript += `\n PRIMARY KEY (${pkFieldNames})`;
}
} }
sqlScript += '\n);\n'; sqlScript += '\n);\n';
// Add table comment (only for databases that support COMMENT ON syntax) // Add table comment
const supportsCommentOn = if (table.comments) {
databaseTypesWithCommentSupport.includes(targetDatabaseType); sqlScript += `COMMENT ON TABLE ${tableName} IS '${table.comments.replace(/'/g, "''")}';\n`;
if (table.comments && supportsCommentOn) {
sqlScript += `COMMENT ON TABLE ${tableName} IS '${escapeSQLComment(table.comments)}';\n`;
} }
table.fields.forEach((field) => { table.fields.forEach((field) => {
// Add column comment (only for databases that support COMMENT ON syntax) // Add column comment
if (field.comments && supportsCommentOn) { if (field.comments) {
const quotedFieldName = getQuotedFieldName( sqlScript += `COMMENT ON COLUMN ${tableName}.${field.name} IS '${field.comments.replace(/'/g, "''")}';\n`;
field.name,
isDBMLFlow
);
sqlScript += `COMMENT ON COLUMN ${tableName}.${quotedFieldName} IS '${escapeSQLComment(field.comments)}';\n`;
} }
}); });
// Generate SQL for indexes // Generate SQL for indexes
table.indexes.forEach((index) => { table.indexes.forEach((index) => {
// Skip the primary key index (it's already handled as a constraint) const fieldNames = index.fieldIds
if (index.isPrimaryKey) { .map(
return; (fieldId) =>
} table.fields.find((field) => field.id === fieldId)?.name
// Get the fields for this index
const indexFields = index.fieldIds
.map((fieldId) => table.fields.find((f) => f.id === fieldId))
.filter(
(field): field is NonNullable<typeof field> =>
field !== undefined
);
// Skip if this index exactly matches the primary key fields
// This prevents creating redundant indexes for composite primary keys
if (
primaryKeyFields.length > 0 &&
primaryKeyFields.length === indexFields.length &&
primaryKeyFields.every((pk) =>
indexFields.some((field) => field.id === pk.id)
) )
) { .filter(Boolean)
return; // Skip this index as it's redundant with the primary key
}
const fieldNames = indexFields
.map((field) => getQuotedFieldName(field.name, isDBMLFlow))
.join(', '); .join(', ');
if (fieldNames) { if (fieldNames) {
const rawIndexName = const indexName = table.schema
table.schema && !isDBMLFlow ? `${table.schema}_${index.name}`
? `${table.schema}_${index.name}` : index.name;
: index.name;
// Quote index name if it contains special characters
// For DBML flow, also quote if contains special characters
const needsQuoting = /[^a-zA-Z0-9_]/.test(rawIndexName);
const indexName = needsQuoting
? `"${rawIndexName}"`
: rawIndexName;
sqlScript += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName} (${fieldNames});\n`; sqlScript += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName} (${fieldNames});\n`;
} }
}); });
@@ -644,18 +435,13 @@ export const exportBaseSQL = ({
return; return;
} }
const fkTableName = getQuotedTableName(fkTable, isDBMLFlow); const fkTableName = fkTable.schema
const refTableName = getQuotedTableName(refTable, isDBMLFlow); ? `${fkTable.schema}.${fkTable.name}`
const quotedFkFieldName = getQuotedFieldName( : fkTable.name;
fkField.name, const refTableName = refTable.schema
isDBMLFlow ? `${refTable.schema}.${refTable.name}`
); : refTable.name;
const quotedRefFieldName = getQuotedFieldName( sqlScript += `ALTER TABLE ${fkTableName} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${fkField.name}) REFERENCES ${refTableName} (${refField.name});\n`;
refField.name,
isDBMLFlow
);
sqlScript += `ALTER TABLE ${fkTableName} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${quotedFkFieldName}) REFERENCES ${refTableName} (${quotedRefFieldName});\n`;
} }
}); });

View File

@@ -1,21 +0,0 @@
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,
};
});
};

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