mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-02 13:03:17 +00:00
Compare commits
99 Commits
jf/fix_fk_
...
jf/edit-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fdc2ccd91 | ||
|
|
8954d893bb | ||
|
|
1a6688e85e | ||
|
|
5e81c1848a | ||
|
|
2bd9ca25b2 | ||
|
|
b016a70691 | ||
|
|
a0fb1ed08b | ||
|
|
ffddcdcc98 | ||
|
|
fe9ef275b8 | ||
|
|
df89f0b6b9 | ||
|
|
534d2858af | ||
|
|
2a64deebb8 | ||
|
|
e5e1d59327 | ||
|
|
aa290615ca | ||
|
|
ec6e46fe81 | ||
|
|
ac128d67de | ||
|
|
07937a2f51 | ||
|
|
d8e0bc7db8 | ||
|
|
1ce265781b | ||
|
|
60c5675cbf | ||
|
|
66b086378c | ||
|
|
abd2a6ccbe | ||
|
|
459c5f1ce3 | ||
|
|
44be48ff3a | ||
|
|
ad8e34483f | ||
|
|
215d57979d | ||
|
|
ec3719ebce | ||
|
|
0a5874a69b | ||
|
|
7e0fdd1595 | ||
|
|
2531a7023f | ||
|
|
73daf0df21 | ||
|
|
c77c983989 | ||
|
|
0aaa451479 | ||
|
|
b697e26170 | ||
|
|
04d91c67b1 | ||
|
|
d0dee84970 | ||
|
|
b4ccfcdcde | ||
|
|
1759b0b9f2 | ||
|
|
ab4845c772 | ||
|
|
0545b41140 | ||
|
|
4520f8b1f7 | ||
|
|
712bdf5b95 | ||
|
|
d7c9536272 | ||
|
|
815a52f192 | ||
|
|
f1a4298362 | ||
|
|
b8f2141bd2 | ||
|
|
eaebe34768 | ||
|
|
0d623a86b1 | ||
|
|
19fd94c6bd | ||
|
|
0da3caeeac | ||
|
|
cb2ba66233 | ||
|
|
8a2267281b | ||
|
|
41ba251377 | ||
|
|
e9c5442d9d | ||
|
|
4f1d3295c0 | ||
|
|
5936500ca0 | ||
|
|
43fc1d7fc2 | ||
|
|
8dfa7cc62e | ||
|
|
23e93bfd01 | ||
|
|
16f9f4671e | ||
|
|
0c300e5e72 | ||
|
|
b9a1e78b53 | ||
|
|
337f7cdab4 | ||
|
|
1b0390f0b7 | ||
|
|
bc52933b58 | ||
|
|
2fdad2344c | ||
|
|
0c7eaa2df2 | ||
|
|
a5f8e56b3c | ||
|
|
8ffde62c1a | ||
|
|
39247b77a2 | ||
|
|
984b2aeee2 | ||
|
|
eed104be5b | ||
|
|
00bd535b3c | ||
|
|
18e914242f | ||
|
|
e68837a34a | ||
|
|
b30162d98b | ||
|
|
dba372d25a | ||
|
|
2eb48e75d3 | ||
|
|
867903cd5f | ||
|
|
8aeb1df0ad | ||
|
|
6bea827293 | ||
|
|
a119854da7 | ||
|
|
bfbfd7b843 | ||
|
|
0ca7008735 | ||
|
|
4bc71c52ff | ||
|
|
8f27f10dec | ||
|
|
a93ec2cab9 | ||
|
|
386e40a0bf | ||
|
|
bda150d4b6 | ||
|
|
87836e53d1 | ||
|
|
7e0483f1a5 | ||
|
|
309ee9cb0f | ||
|
|
79b885502e | ||
|
|
745bdee86d | ||
|
|
08eb9cc55f | ||
|
|
778f85d492 | ||
|
|
fb92be7d3e | ||
|
|
6df588f40e | ||
|
|
b46ed58dff |
2
.github/workflows/cla.yaml
vendored
2
.github/workflows/cla.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write # this can be 'read' if the signatures are in remote repository
|
||||
contents: read
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
|
||||
113
CHANGELOG.md
113
CHANGELOG.md
@@ -1,5 +1,118 @@
|
||||
# Changelog
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
14
index.html
14
index.html
@@ -4,8 +4,9 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="max-image-preview:large" />
|
||||
<meta name="robots" content="noindex, max-image-preview:large" />
|
||||
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
|
||||
<link rel="canonical" href="https://chartdb.io" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
@@ -15,14 +16,19 @@
|
||||
<script src="/config.js"></script>
|
||||
<script>
|
||||
// Load analytics only if not disabled
|
||||
(function() {
|
||||
const disableAnalytics = (window.env && window.env.DISABLE_ANALYTICS === 'true') ||
|
||||
(typeof process !== 'undefined' && process.env && process.env.VITE_DISABLE_ANALYTICS === 'true');
|
||||
(function () {
|
||||
const disableAnalytics =
|
||||
(window.env && window.env.DISABLE_ANALYTICS === 'true') ||
|
||||
(typeof process !== 'undefined' &&
|
||||
process.env &&
|
||||
process.env.VITE_DISABLE_ANALYTICS === 'true');
|
||||
|
||||
if (!disableAnalytics) {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.usefathom.com/script.js';
|
||||
script.setAttribute('data-site', 'PRHIVBNN');
|
||||
script.setAttribute('data-canonical', 'false');
|
||||
script.setAttribute('data-spa', 'auto');
|
||||
script.defer = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
887
package-lock.json
generated
887
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chartdb",
|
||||
"private": true,
|
||||
"version": "1.13.2",
|
||||
"version": "1.15.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^0.0.51",
|
||||
"@dbml/core": "^3.9.5",
|
||||
"@dbml/core": "^3.13.9",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
@@ -26,24 +26,24 @@
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-menubar": "^1.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@xyflow/react": "^12.3.1",
|
||||
"@xyflow/react": "^12.8.2",
|
||||
"ahooks": "^3.8.1",
|
||||
"ai": "^3.3.14",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /
|
||||
|
||||
Sitemap: https://app.chartdb.io/sitemap.xml
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
137
src/components/button/button-with-alternatives.tsx
Normal file
137
src/components/button/button-with-alternatives.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import React from 'react';
|
||||
import { ChevronDownIcon } from '@radix-ui/react-icons';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { type VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { buttonVariants } from './button-variants';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/dropdown-menu/dropdown-menu';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
|
||||
export interface ButtonAlternative {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
className?: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export interface ButtonWithAlternativesProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
alternatives: Array<ButtonAlternative>;
|
||||
dropdownTriggerClassName?: string;
|
||||
chevronDownIconClassName?: string;
|
||||
}
|
||||
|
||||
const ButtonWithAlternatives = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ButtonWithAlternativesProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
alternatives,
|
||||
children,
|
||||
onClick,
|
||||
dropdownTriggerClassName,
|
||||
chevronDownIconClassName,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
const hasAlternatives = (alternatives?.length ?? 0) > 0;
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-stretch">
|
||||
<Comp
|
||||
className={cn(
|
||||
buttonVariants({ variant, size }),
|
||||
{ 'rounded-r-none': hasAlternatives },
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Comp>
|
||||
{hasAlternatives ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
buttonVariants({ variant, size }),
|
||||
'rounded-l-none border-l border-l-primary/5 px-2 min-w-0',
|
||||
className?.includes('h-') &&
|
||||
className.match(/h-\d+/)?.[0],
|
||||
className?.includes('text-') &&
|
||||
className.match(/text-\w+/)?.[0],
|
||||
dropdownTriggerClassName
|
||||
)}
|
||||
type="button"
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
'size-4 shrink-0',
|
||||
chevronDownIconClassName
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{alternatives.map((alternative, index) => {
|
||||
const menuItem = (
|
||||
<DropdownMenuItem
|
||||
key={index}
|
||||
onClick={alternative.onClick}
|
||||
disabled={alternative.disabled}
|
||||
className={cn(alternative.className)}
|
||||
>
|
||||
<span className="flex w-full items-center justify-between gap-2">
|
||||
{alternative.label}
|
||||
{alternative.icon}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
|
||||
if (alternative.tooltip) {
|
||||
return (
|
||||
<Tooltip key={index}>
|
||||
<TooltipTrigger asChild>
|
||||
{menuItem}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
{alternative.tooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
ButtonWithAlternatives.displayName = 'ButtonWithAlternatives';
|
||||
|
||||
export { ButtonWithAlternatives };
|
||||
@@ -31,6 +31,7 @@ export interface CodeSnippetAction {
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface CodeSnippetProps {
|
||||
@@ -43,6 +44,8 @@ export interface CodeSnippetProps {
|
||||
isComplete?: boolean;
|
||||
editorProps?: React.ComponentProps<EditorType>;
|
||||
actions?: CodeSnippetAction[];
|
||||
actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left';
|
||||
allowCopy?: boolean;
|
||||
}
|
||||
|
||||
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
@@ -56,6 +59,8 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
isComplete = true,
|
||||
editorProps,
|
||||
actions,
|
||||
actionsTooltipSide,
|
||||
allowCopy = true,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const monaco = useMonaco();
|
||||
@@ -129,33 +134,37 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
<Suspense fallback={<Spinner />}>
|
||||
{isComplete ? (
|
||||
<div className="absolute right-1 top-1 z-10 flex flex-col gap-1">
|
||||
<Tooltip
|
||||
onOpenChange={setTooltipOpen}
|
||||
open={isCopied || tooltipOpen}
|
||||
>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
className="h-fit p-1.5"
|
||||
variant="outline"
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
{isCopied ? (
|
||||
<CopyCheck size={16} />
|
||||
) : (
|
||||
<Copy size={16} />
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t(
|
||||
isCopied
|
||||
? 'copied'
|
||||
: 'copy_to_clipboard'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{allowCopy ? (
|
||||
<Tooltip
|
||||
onOpenChange={setTooltipOpen}
|
||||
open={isCopied || tooltipOpen}
|
||||
>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
className="h-fit p-1.5"
|
||||
variant="outline"
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
{isCopied ? (
|
||||
<CopyCheck size={16} />
|
||||
) : (
|
||||
<Copy size={16} />
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side={actionsTooltipSide}
|
||||
>
|
||||
{t(
|
||||
isCopied
|
||||
? 'copied'
|
||||
: 'copy_to_clipboard'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
|
||||
{actions &&
|
||||
actions.length > 0 &&
|
||||
@@ -164,7 +173,10 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
className="h-fit p-1.5"
|
||||
className={cn(
|
||||
'h-fit p-1.5',
|
||||
action.className
|
||||
)}
|
||||
variant="outline"
|
||||
onClick={action.onClick}
|
||||
>
|
||||
@@ -174,7 +186,9 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<TooltipContent
|
||||
side={actionsTooltipSide}
|
||||
>
|
||||
{action.label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
51
src/components/code-snippet/dbml/utils.ts
Normal file
51
src/components/code-snippet/dbml/utils.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
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();
|
||||
}
|
||||
};
|
||||
@@ -37,18 +37,28 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
|
||||
const datatypePattern = dataTypesNames.join('|');
|
||||
|
||||
monaco.languages.setMonarchTokensProvider('dbml', {
|
||||
keywords: ['Table', 'Ref', 'Indexes'],
|
||||
keywords: ['Table', 'Ref', 'Indexes', 'Note', 'Enum'],
|
||||
datatypes: dataTypesNames,
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/\b(Table|Ref|Indexes)\b/, 'keyword'],
|
||||
[
|
||||
/\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',
|
||||
],
|
||||
[/\[.*?\]/, 'annotation'],
|
||||
[/'''/, 'string', '@tripleQuoteString'],
|
||||
[/".*?"/, 'string'],
|
||||
[/'.*?'/, 'string'],
|
||||
[/`.*?`/, 'string'],
|
||||
[/[{}]/, 'delimiter'],
|
||||
[/[<>]/, 'operator'],
|
||||
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching
|
||||
],
|
||||
tripleQuoteString: [
|
||||
[/[^']+/, 'string'],
|
||||
[/'''/, 'string', '@pop'],
|
||||
[/'/, 'string'],
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,27 +5,45 @@ import {
|
||||
PopoverTrigger,
|
||||
} from '@/components/popover/popover';
|
||||
import { colorOptions } from '@/lib/colors';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface ColorPickerProps {
|
||||
color: string;
|
||||
onChange: (color: string) => void;
|
||||
disabled?: boolean;
|
||||
popoverOnMouseDown?: (e: React.MouseEvent) => void;
|
||||
popoverOnClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export const ColorPicker = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverTrigger>,
|
||||
ColorPickerProps
|
||||
>(({ color, onChange }, ref) => {
|
||||
>(({ color, onChange, disabled, popoverOnMouseDown, popoverOnClick }, ref) => {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild ref={ref}>
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
{...(disabled ? { onClick: (e) => e.preventDefault() } : {})}
|
||||
>
|
||||
<div
|
||||
className="h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
|
||||
className={cn(
|
||||
'h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md',
|
||||
{
|
||||
'hover:shadow-none cursor-default': disabled,
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-fit">
|
||||
<PopoverContent
|
||||
className="w-fit"
|
||||
onMouseDown={popoverOnMouseDown}
|
||||
onClick={popoverOnClick}
|
||||
>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{colorOptions.map((option) => (
|
||||
<div
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Cross2Icon } from '@radix-ui/react-icons';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ScrollArea } from '../scroll-area/scroll-area';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
@@ -32,28 +33,75 @@ const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||
showClose?: boolean;
|
||||
showBack?: boolean;
|
||||
backButtonClassName?: string;
|
||||
blurBackground?: boolean;
|
||||
forceOverlay?: boolean;
|
||||
onBackClick?: () => void;
|
||||
}
|
||||
>(({ className, children, showClose, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<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}
|
||||
{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>
|
||||
));
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
showClose,
|
||||
showBack,
|
||||
onBackClick,
|
||||
backButtonClassName,
|
||||
blurBackground,
|
||||
forceOverlay,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<DialogPortal>
|
||||
{forceOverlay ? (
|
||||
<div
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
{
|
||||
'bg-black/80': !blurBackground,
|
||||
'bg-black/30 backdrop-blur-sm': blurBackground,
|
||||
}
|
||||
)}
|
||||
data-state="open"
|
||||
/>
|
||||
) : null}
|
||||
<DialogOverlay
|
||||
className={cn({
|
||||
'bg-black/30 backdrop-blur-sm': blurBackground,
|
||||
})}
|
||||
/>
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showBack && (
|
||||
<button
|
||||
onClick={() => onBackClick?.()}
|
||||
className={cn(
|
||||
'absolute left-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground',
|
||||
backButtonClassName
|
||||
)}
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
{showClose && (
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<Cross2Icon className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
);
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
|
||||
@@ -2,16 +2,13 @@ import React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface SelectBoxOption {
|
||||
regex?: string;
|
||||
extractRegex?: RegExp;
|
||||
group?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SelectBoxProps {
|
||||
@@ -53,6 +54,10 @@ export interface SelectBoxProps {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
popoverClassName?: string;
|
||||
readonly?: boolean;
|
||||
footerButtons?: React.ReactNode;
|
||||
commandOnMouseDown?: (e: React.MouseEvent) => void;
|
||||
commandOnClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
@@ -78,6 +83,10 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
popoverClassName,
|
||||
readonly,
|
||||
footerButtons,
|
||||
commandOnMouseDown,
|
||||
commandOnClick,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -93,6 +102,12 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
(isOpen: boolean) => {
|
||||
setOpen?.(isOpen);
|
||||
setIsOpen(isOpen);
|
||||
|
||||
if (isOpen) {
|
||||
setSearchTerm('');
|
||||
}
|
||||
|
||||
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
|
||||
},
|
||||
[setOpen]
|
||||
);
|
||||
@@ -146,18 +161,20 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleSelect(option.value);
|
||||
}}
|
||||
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
|
||||
>
|
||||
<Cross2Icon />
|
||||
</span>
|
||||
{!readonly ? (
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleSelect(option.value);
|
||||
}}
|
||||
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
|
||||
>
|
||||
<Cross2Icon />
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
)),
|
||||
[options, value, handleSelect, oneLine, keepOrder]
|
||||
[options, value, handleSelect, oneLine, keepOrder, readonly]
|
||||
);
|
||||
|
||||
const isAllSelected = React.useMemo(
|
||||
@@ -227,9 +244,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
onSelect={() =>
|
||||
handleSelect(
|
||||
option.value,
|
||||
matches?.map((match) => match.toString())
|
||||
matches?.map((match) => match?.toString())
|
||||
)
|
||||
}
|
||||
onMouseDown={commandOnMouseDown}
|
||||
onClick={commandOnClick}
|
||||
>
|
||||
{multiple && (
|
||||
<div
|
||||
@@ -244,6 +263,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-1 items-center truncate">
|
||||
{option.icon ? (
|
||||
<span className="mr-2 shrink-0">
|
||||
{option.icon}
|
||||
</span>
|
||||
) : null}
|
||||
<span>
|
||||
{isRegexMatch ? searchTerm : option.label}
|
||||
{!isRegexMatch && optionSuffix
|
||||
@@ -270,7 +294,15 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
</CommandItem>
|
||||
);
|
||||
},
|
||||
[value, multiple, searchTerm, handleSelect, optionSuffix]
|
||||
[
|
||||
value,
|
||||
multiple,
|
||||
searchTerm,
|
||||
handleSelect,
|
||||
optionSuffix,
|
||||
commandOnClick,
|
||||
commandOnMouseDown,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -278,7 +310,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
|
||||
<div
|
||||
className={cn(
|
||||
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`,
|
||||
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''} ${readonly ? 'pointer-events-none' : ''}`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -348,6 +380,8 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
popoverClassName
|
||||
)}
|
||||
align="center"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Command
|
||||
filter={(value, search, keywords) => {
|
||||
@@ -437,6 +471,9 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
{footerButtons ? (
|
||||
<div className="border-t">{footerButtons}</div>
|
||||
) : null}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
@@ -29,6 +29,7 @@ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||
const SIDEBAR_WIDTH = '16rem';
|
||||
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||
const SIDEBAR_WIDTH_ICON = '3rem';
|
||||
const SIDEBAR_WIDTH_ICON_EXTENDED = '4rem';
|
||||
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
||||
|
||||
type SidebarContext = {
|
||||
@@ -142,6 +143,8 @@ const SidebarProvider = React.forwardRef<
|
||||
{
|
||||
'--sidebar-width': SIDEBAR_WIDTH,
|
||||
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
||||
'--sidebar-width-icon-extended':
|
||||
SIDEBAR_WIDTH_ICON_EXTENDED,
|
||||
...style,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
@@ -166,7 +169,7 @@ const Sidebar = React.forwardRef<
|
||||
React.ComponentProps<'div'> & {
|
||||
side?: 'left' | 'right';
|
||||
variant?: 'sidebar' | 'floating' | 'inset';
|
||||
collapsible?: 'offcanvas' | 'icon' | 'none';
|
||||
collapsible?: 'offcanvas' | 'icon' | 'icon-extended' | 'none';
|
||||
}
|
||||
>(
|
||||
(
|
||||
@@ -245,8 +248,8 @@ const Sidebar = React.forwardRef<
|
||||
'group-data-[collapsible=offcanvas]:w-0',
|
||||
'group-data-[side=right]:rotate-180',
|
||||
variant === 'floating' || variant === 'inset'
|
||||
? '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]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4))]'
|
||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended]'
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
@@ -257,8 +260,8 @@ const Sidebar = React.forwardRef<
|
||||
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
||||
// Adjust the padding for floating and inset variants.
|
||||
variant === 'floating' || variant === 'inset'
|
||||
? '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-[side=left]:border-r group-data-[side=right]:border-l',
|
||||
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4)_+2px)]'
|
||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended] group-data-[side=left]:border-r group-data-[side=right]:border-l',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -421,7 +424,7 @@ const SidebarContent = React.forwardRef<
|
||||
ref={ref}
|
||||
data-sidebar="content"
|
||||
className={cn(
|
||||
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
|
||||
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden group-data-[collapsible=icon-extended]:overflow-hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -461,6 +464,7 @@ const SidebarGroupLabel = React.forwardRef<
|
||||
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',
|
||||
'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
|
||||
)}
|
||||
{...props}
|
||||
@@ -483,7 +487,7 @@ const SidebarGroupAction = React.forwardRef<
|
||||
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
// Increases the hit area of the button on mobile.
|
||||
'after:absolute after:-inset-2 after:md:hidden',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -532,7 +536,7 @@ const SidebarMenuItem = React.forwardRef<
|
||||
SidebarMenuItem.displayName = 'SidebarMenuItem';
|
||||
|
||||
const sidebarMenuButtonVariants = cva(
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon-extended]:h-auto group-data-[collapsible=icon-extended]:flex-col group-data-[collapsible=icon-extended]:gap-1 group-data-[collapsible=icon-extended]:p-2 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate group-data-[collapsible=icon-extended]:[&>span]:w-full group-data-[collapsible=icon-extended]:[&>span]:text-center group-data-[collapsible=icon-extended]:[&>span]:text-[10px] group-data-[collapsible=icon-extended]:[&>span]:leading-tight [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -636,7 +640,7 @@ const SidebarMenuAction = React.forwardRef<
|
||||
'peer-data-[size=sm]/menu-button:top-1',
|
||||
'peer-data-[size=default]/menu-button:top-1.5',
|
||||
'peer-data-[size=lg]/menu-button:top-2.5',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||
showOnHover &&
|
||||
'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
|
||||
@@ -753,7 +757,7 @@ const SidebarMenuSubButton = React.forwardRef<
|
||||
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
||||
size === 'sm' && 'text-xs',
|
||||
size === 'md' && 'text-sm',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -13,15 +13,17 @@ const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
// <TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
// </TooltipPrimitive.Portal>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
|
||||
@@ -14,6 +14,16 @@ export interface CanvasContext {
|
||||
overlapGraph: Graph<string>;
|
||||
setShowFilter: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
showFilter: boolean;
|
||||
editTableModeTable: {
|
||||
tableId: string;
|
||||
fieldId?: string;
|
||||
} | null;
|
||||
setEditTableModeTable: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
tableId: string;
|
||||
fieldId?: string;
|
||||
} | null>
|
||||
>;
|
||||
}
|
||||
|
||||
export const canvasContext = createContext<CanvasContext>({
|
||||
@@ -23,4 +33,6 @@ export const canvasContext = createContext<CanvasContext>({
|
||||
overlapGraph: createGraph(),
|
||||
setShowFilter: emptyFn,
|
||||
showFilter: false,
|
||||
editTableModeTable: null,
|
||||
setEditTableModeTable: emptyFn,
|
||||
});
|
||||
|
||||
@@ -1,27 +1,59 @@
|
||||
import React, { type ReactNode, useCallback, useState } from 'react';
|
||||
import React, {
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { canvasContext } from './canvas-context';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import {
|
||||
adjustTablePositions,
|
||||
shouldShowTablesBySchemaFilter,
|
||||
} from '@/lib/domain/db-table';
|
||||
import { adjustTablePositions } from '@/lib/domain/db-table';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
|
||||
import type { Graph } from '@/lib/graph';
|
||||
import { createGraph } from '@/lib/graph';
|
||||
import { useDiagramFilter } from '../diagram-filter-context/use-diagram-filter';
|
||||
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
|
||||
interface CanvasProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
const { tables, relationships, updateTablesState, filteredSchemas } =
|
||||
useChartDB();
|
||||
const {
|
||||
tables,
|
||||
relationships,
|
||||
updateTablesState,
|
||||
databaseType,
|
||||
areas,
|
||||
diagramId,
|
||||
} = useChartDB();
|
||||
const { filter, loading: filterLoading } = useDiagramFilter();
|
||||
const { fitView } = useReactFlow();
|
||||
const [overlapGraph, setOverlapGraph] =
|
||||
useState<Graph<string>>(createGraph());
|
||||
const [editTableModeTable, setEditTableModeTable] = useState<{
|
||||
tableId: string;
|
||||
fieldId?: string;
|
||||
} | null>(null);
|
||||
|
||||
const [showFilter, setShowFilter] = useState(false);
|
||||
const diagramIdActiveFilterRef = useRef<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (filterLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagramIdActiveFilterRef.current === diagramId) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagramIdActiveFilterRef.current = diagramId;
|
||||
|
||||
setShowFilter(true);
|
||||
}, [filterLoading, diagramId]);
|
||||
|
||||
const reorderTables = useCallback(
|
||||
(
|
||||
@@ -32,9 +64,19 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
const newTables = adjustTablePositions({
|
||||
relationships,
|
||||
tables: tables.filter((table) =>
|
||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
||||
filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
})
|
||||
),
|
||||
mode: 'all', // Use 'all' mode for manual reordering
|
||||
areas,
|
||||
mode: 'all',
|
||||
});
|
||||
|
||||
const updatedOverlapGraph = findOverlappingTables({
|
||||
@@ -69,7 +111,15 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
});
|
||||
}, 500);
|
||||
},
|
||||
[filteredSchemas, relationships, tables, updateTablesState, fitView]
|
||||
[
|
||||
filter,
|
||||
relationships,
|
||||
tables,
|
||||
updateTablesState,
|
||||
fitView,
|
||||
databaseType,
|
||||
areas,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -81,6 +131,8 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
overlapGraph,
|
||||
setShowFilter,
|
||||
showFilter,
|
||||
editTableModeTable,
|
||||
setEditTableModeTable,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -78,8 +78,8 @@ export interface ChartDBContext {
|
||||
events: EventEmitter<ChartDBEvent>;
|
||||
readonly?: boolean;
|
||||
|
||||
filteredSchemas?: string[];
|
||||
filterSchemas: (schemaIds: string[]) => void;
|
||||
highlightedCustomType?: DBCustomType;
|
||||
highlightCustomTypeId: (id?: string) => void;
|
||||
|
||||
// General operations
|
||||
updateDiagramId: (id: string) => Promise<void>;
|
||||
@@ -92,6 +92,10 @@ export interface ChartDBContext {
|
||||
updateDiagramUpdatedAt: () => Promise<void>;
|
||||
clearDiagramData: () => Promise<void>;
|
||||
deleteDiagram: () => Promise<void>;
|
||||
updateDiagramData: (
|
||||
diagram: Diagram,
|
||||
options?: { forceUpdateStorage?: boolean }
|
||||
) => Promise<void>;
|
||||
|
||||
// Database type operations
|
||||
updateDatabaseType: (databaseType: DatabaseType) => Promise<void>;
|
||||
@@ -277,11 +281,6 @@ export interface ChartDBContext {
|
||||
customType: Partial<DBCustomType>,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
|
||||
// Filters
|
||||
hiddenTableIds?: string[];
|
||||
addHiddenTableId: (tableId: string) => Promise<void>;
|
||||
removeHiddenTableId: (tableId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const chartDBContext = createContext<ChartDBContext>({
|
||||
@@ -294,8 +293,7 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
areas: [],
|
||||
customTypes: [],
|
||||
schemas: [],
|
||||
filteredSchemas: [],
|
||||
filterSchemas: emptyFn,
|
||||
highlightCustomTypeId: emptyFn,
|
||||
currentDiagram: {
|
||||
id: '',
|
||||
name: '',
|
||||
@@ -313,6 +311,7 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
loadDiagramFromData: emptyFn,
|
||||
clearDiagramData: emptyFn,
|
||||
deleteDiagram: emptyFn,
|
||||
updateDiagramData: emptyFn,
|
||||
|
||||
// Database type operations
|
||||
updateDatabaseType: emptyFn,
|
||||
@@ -377,9 +376,4 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
removeCustomType: emptyFn,
|
||||
removeCustomTypes: emptyFn,
|
||||
updateCustomType: emptyFn,
|
||||
|
||||
// Filters
|
||||
hiddenTableIds: [],
|
||||
addHiddenTableId: emptyFn,
|
||||
removeHiddenTableId: emptyFn,
|
||||
});
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import { deepCopy, generateId } from '@/lib/utils';
|
||||
import { randomColor } from '@/lib/colors';
|
||||
import { defaultTableColor, defaultAreaColor, viewColor } from '@/lib/colors';
|
||||
import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
|
||||
import { chartDBContext } from './chartdb-context';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBIndex } from '@/lib/domain/db-index';
|
||||
import {
|
||||
getTableIndexesWithPrimaryKey,
|
||||
type DBIndex,
|
||||
} from '@/lib/domain/db-index';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||
@@ -17,7 +20,6 @@ import {
|
||||
databasesWithSchemas,
|
||||
schemaNameToSchemaId,
|
||||
} from '@/lib/domain/db-schema';
|
||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import { useEventEmitter } from 'ahooks';
|
||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||
@@ -29,7 +31,6 @@ import {
|
||||
DBCustomTypeKind,
|
||||
type DBCustomType,
|
||||
} from '@/lib/domain/db-custom-type';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
|
||||
export interface ChartDBProviderProps {
|
||||
diagram?: Diagram;
|
||||
@@ -40,16 +41,11 @@ export const ChartDBProvider: React.FC<
|
||||
React.PropsWithChildren<ChartDBProviderProps>
|
||||
> = ({ children, diagram, readonly: readonlyProp }) => {
|
||||
const { hasDiff } = useDiff();
|
||||
let db = useStorage();
|
||||
const storageDB = useStorage();
|
||||
const events = useEventEmitter<ChartDBEvent>();
|
||||
const { setSchemasFilter, schemasFilter } = useLocalConfig();
|
||||
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
||||
useRedoUndoStack();
|
||||
const {
|
||||
getHiddenTablesForDiagram,
|
||||
hideTableForDiagram,
|
||||
unhideTableForDiagram,
|
||||
} = useConfig();
|
||||
|
||||
const [diagramId, setDiagramId] = useState('');
|
||||
const [diagramName, setDiagramName] = useState('');
|
||||
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
||||
@@ -71,9 +67,12 @@ export const ChartDBProvider: React.FC<
|
||||
const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
|
||||
diagram?.customTypes ?? []
|
||||
);
|
||||
const [hiddenTableIds, setHiddenTableIds] = useState<string[]>([]);
|
||||
|
||||
const { events: diffEvents } = useDiff();
|
||||
|
||||
const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
|
||||
useState<string>();
|
||||
|
||||
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
|
||||
const { tablesAdded, fieldsAdded, relationshipsAdded } = event.data;
|
||||
setTables((tables) =>
|
||||
@@ -92,25 +91,16 @@ export const ChartDBProvider: React.FC<
|
||||
|
||||
diffEvents.useSubscription(diffCalculatedHandler);
|
||||
|
||||
// Sync hiddenTableIds with config
|
||||
useEffect(() => {
|
||||
if (diagramId) {
|
||||
const hiddenTables = getHiddenTablesForDiagram(diagramId);
|
||||
setHiddenTableIds(hiddenTables);
|
||||
}
|
||||
}, [diagramId, getHiddenTablesForDiagram]);
|
||||
|
||||
const defaultSchemaName = defaultSchemas[databaseType];
|
||||
const defaultSchemaName = useMemo(
|
||||
() => defaultSchemas[databaseType],
|
||||
[databaseType]
|
||||
);
|
||||
|
||||
const readonly = useMemo(
|
||||
() => readonlyProp ?? hasDiff ?? false,
|
||||
[readonlyProp, hasDiff]
|
||||
);
|
||||
|
||||
if (readonly) {
|
||||
db = storageInitialValue;
|
||||
}
|
||||
|
||||
const schemas = useMemo(
|
||||
() =>
|
||||
databasesWithSchemas.includes(databaseType)
|
||||
@@ -121,9 +111,11 @@ export const ChartDBProvider: React.FC<
|
||||
.filter((schema) => !!schema) as string[]
|
||||
),
|
||||
]
|
||||
.sort((a, b) =>
|
||||
a === defaultSchemaName ? -1 : a.localeCompare(b)
|
||||
)
|
||||
.sort((a, b) => {
|
||||
if (a === defaultSchemaName) return -1;
|
||||
if (b === defaultSchemaName) return 1;
|
||||
return a.localeCompare(b);
|
||||
})
|
||||
.map(
|
||||
(schema): DBSchema => ({
|
||||
id: schemaNameToSchemaId(schema),
|
||||
@@ -137,34 +129,11 @@ export const ChartDBProvider: React.FC<
|
||||
[tables, defaultSchemaName, databaseType]
|
||||
);
|
||||
|
||||
const filterSchemas: ChartDBContext['filterSchemas'] = useCallback(
|
||||
(schemaIds) => {
|
||||
setSchemasFilter((prev) => ({
|
||||
...prev,
|
||||
[diagramId]: schemaIds,
|
||||
}));
|
||||
},
|
||||
[diagramId, setSchemasFilter]
|
||||
const db = useMemo(
|
||||
() => (readonly ? storageInitialValue : storageDB),
|
||||
[storageDB, readonly]
|
||||
);
|
||||
|
||||
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(
|
||||
() => ({
|
||||
id: diagramId,
|
||||
@@ -376,12 +345,17 @@ export const ChartDBProvider: React.FC<
|
||||
},
|
||||
],
|
||||
indexes: [],
|
||||
color: randomColor(),
|
||||
color: attributes?.isView ? viewColor : defaultTableColor,
|
||||
createdAt: Date.now(),
|
||||
isView: false,
|
||||
order: tables.length,
|
||||
...attributes,
|
||||
};
|
||||
|
||||
table.indexes = getTableIndexesWithPrimaryKey({
|
||||
table,
|
||||
});
|
||||
|
||||
await addTable(table);
|
||||
|
||||
return table;
|
||||
@@ -673,17 +647,30 @@ export const ChartDBProvider: React.FC<
|
||||
options = { updateHistory: true }
|
||||
) => {
|
||||
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) =>
|
||||
tables.map((table) =>
|
||||
table.id === tableId
|
||||
? {
|
||||
...table,
|
||||
fields: table.fields.map((f) =>
|
||||
f.id === fieldId ? { ...f, ...field } : f
|
||||
),
|
||||
}
|
||||
: table
|
||||
)
|
||||
tables.map((table) => {
|
||||
if (table.id === tableId) {
|
||||
return updateTableFn(table);
|
||||
}
|
||||
|
||||
return table;
|
||||
})
|
||||
);
|
||||
|
||||
const table = await db.getTable({ diagramId, id: tableId });
|
||||
@@ -698,10 +685,7 @@ export const ChartDBProvider: React.FC<
|
||||
db.updateTable({
|
||||
id: tableId,
|
||||
attributes: {
|
||||
...table,
|
||||
fields: table.fields.map((f) =>
|
||||
f.id === fieldId ? { ...f, ...field } : f
|
||||
),
|
||||
...updateTableFn(table),
|
||||
},
|
||||
}),
|
||||
]);
|
||||
@@ -728,19 +712,29 @@ export const ChartDBProvider: React.FC<
|
||||
fieldId: string,
|
||||
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 prevField = getField(tableId, fieldId);
|
||||
setTables((tables) =>
|
||||
tables.map((table) =>
|
||||
table.id === tableId
|
||||
? {
|
||||
...table,
|
||||
fields: table.fields.filter(
|
||||
(f) => f.id !== fieldId
|
||||
),
|
||||
}
|
||||
: table
|
||||
)
|
||||
tables.map((table) => {
|
||||
if (table.id === tableId) {
|
||||
return updateTableFn(table);
|
||||
}
|
||||
|
||||
return table;
|
||||
})
|
||||
);
|
||||
|
||||
events.emit({
|
||||
@@ -764,8 +758,7 @@ export const ChartDBProvider: React.FC<
|
||||
db.updateTable({
|
||||
id: tableId,
|
||||
attributes: {
|
||||
...table,
|
||||
fields: table.fields.filter((f) => f.id !== fieldId),
|
||||
...updateTableFn(table),
|
||||
},
|
||||
}),
|
||||
]);
|
||||
@@ -1121,12 +1114,15 @@ export const ChartDBProvider: React.FC<
|
||||
|
||||
const sourceFieldName = sourceField?.name ?? '';
|
||||
|
||||
const targetTable = getTable(targetTableId);
|
||||
const targetTableSchema = targetTable?.schema;
|
||||
|
||||
const relationship: DBRelationship = {
|
||||
id: generateId(),
|
||||
name: `${sourceTableName}_${sourceFieldName}_fk`,
|
||||
sourceSchema: sourceTable?.schema,
|
||||
sourceTableId,
|
||||
targetSchema: sourceTable?.schema,
|
||||
targetSchema: targetTableSchema,
|
||||
targetTableId,
|
||||
sourceFieldId,
|
||||
targetFieldId,
|
||||
@@ -1448,7 +1444,7 @@ export const ChartDBProvider: React.FC<
|
||||
y: 0,
|
||||
width: 300,
|
||||
height: 200,
|
||||
color: randomColor(),
|
||||
color: defaultAreaColor,
|
||||
...attributes,
|
||||
};
|
||||
|
||||
@@ -1531,22 +1527,37 @@ export const ChartDBProvider: React.FC<
|
||||
[db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack]
|
||||
);
|
||||
|
||||
const highlightCustomTypeId = useCallback(
|
||||
(id?: string) => setHighlightedCustomTypeId(id),
|
||||
[setHighlightedCustomTypeId]
|
||||
);
|
||||
|
||||
const highlightedCustomType = useMemo(() => {
|
||||
return highlightedCustomTypeId
|
||||
? customTypes.find((type) => type.id === highlightedCustomTypeId)
|
||||
: undefined;
|
||||
}, [highlightedCustomTypeId, customTypes]);
|
||||
|
||||
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
|
||||
useCallback(
|
||||
async (diagram) => {
|
||||
(diagram) => {
|
||||
setDiagramId(diagram.id);
|
||||
setDiagramName(diagram.name);
|
||||
setDatabaseType(diagram.databaseType);
|
||||
setDatabaseEdition(diagram.databaseEdition);
|
||||
setTables(diagram?.tables ?? []);
|
||||
setRelationships(diagram?.relationships ?? []);
|
||||
setDependencies(diagram?.dependencies ?? []);
|
||||
setAreas(diagram?.areas ?? []);
|
||||
setCustomTypes(diagram?.customTypes ?? []);
|
||||
setTables(diagram.tables ?? []);
|
||||
setRelationships(diagram.relationships ?? []);
|
||||
setDependencies(diagram.dependencies ?? []);
|
||||
setAreas(diagram.areas ?? []);
|
||||
setCustomTypes(diagram.customTypes ?? []);
|
||||
setDiagramCreatedAt(diagram.createdAt);
|
||||
setDiagramUpdatedAt(diagram.updatedAt);
|
||||
setHighlightedCustomTypeId(undefined);
|
||||
|
||||
events.emit({ action: 'load_diagram', data: { diagram } });
|
||||
|
||||
resetRedoStack();
|
||||
resetUndoStack();
|
||||
},
|
||||
[
|
||||
setDiagramId,
|
||||
@@ -1560,13 +1571,26 @@ export const ChartDBProvider: React.FC<
|
||||
setCustomTypes,
|
||||
setDiagramCreatedAt,
|
||||
setDiagramUpdatedAt,
|
||||
setHighlightedCustomTypeId,
|
||||
events,
|
||||
resetRedoStack,
|
||||
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(
|
||||
async (diagramId: string) => {
|
||||
const diagram = await db.getDiagram(diagramId, {
|
||||
const diagram = await storageDB.getDiagram(diagramId, {
|
||||
includeRelationships: true,
|
||||
includeTables: true,
|
||||
includeDependencies: true,
|
||||
@@ -1580,7 +1604,7 @@ export const ChartDBProvider: React.FC<
|
||||
|
||||
return diagram;
|
||||
},
|
||||
[db, loadDiagramFromData]
|
||||
[storageDB, loadDiagramFromData]
|
||||
);
|
||||
|
||||
// Custom type operations
|
||||
@@ -1727,29 +1751,6 @@ export const ChartDBProvider: React.FC<
|
||||
]
|
||||
);
|
||||
|
||||
const addHiddenTableId: ChartDBContext['addHiddenTableId'] = useCallback(
|
||||
async (tableId: string) => {
|
||||
if (!hiddenTableIds.includes(tableId)) {
|
||||
setHiddenTableIds((prev) => [...prev, tableId]);
|
||||
await hideTableForDiagram(diagramId, tableId);
|
||||
}
|
||||
},
|
||||
[hiddenTableIds, diagramId, hideTableForDiagram]
|
||||
);
|
||||
|
||||
const removeHiddenTableId: ChartDBContext['removeHiddenTableId'] =
|
||||
useCallback(
|
||||
async (tableId: string) => {
|
||||
if (hiddenTableIds.includes(tableId)) {
|
||||
setHiddenTableIds((prev) =>
|
||||
prev.filter((id) => id !== tableId)
|
||||
);
|
||||
await unhideTableForDiagram(diagramId, tableId);
|
||||
}
|
||||
},
|
||||
[hiddenTableIds, diagramId, unhideTableForDiagram]
|
||||
);
|
||||
|
||||
return (
|
||||
<chartDBContext.Provider
|
||||
value={{
|
||||
@@ -1762,10 +1763,9 @@ export const ChartDBProvider: React.FC<
|
||||
areas,
|
||||
currentDiagram,
|
||||
schemas,
|
||||
filteredSchemas,
|
||||
events,
|
||||
readonly,
|
||||
filterSchemas,
|
||||
updateDiagramData,
|
||||
updateDiagramId,
|
||||
updateDiagramName,
|
||||
loadDiagram,
|
||||
@@ -1822,9 +1822,8 @@ export const ChartDBProvider: React.FC<
|
||||
removeCustomType,
|
||||
removeCustomTypes,
|
||||
updateCustomType,
|
||||
hiddenTableIds,
|
||||
addHiddenTableId,
|
||||
removeHiddenTableId,
|
||||
highlightCustomTypeId,
|
||||
highlightedCustomType,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -8,23 +8,9 @@ export interface ConfigContext {
|
||||
config?: Partial<ChartDBConfig>;
|
||||
updateFn?: (config: ChartDBConfig) => ChartDBConfig;
|
||||
}) => 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>({
|
||||
config: undefined,
|
||||
updateConfig: emptyFn,
|
||||
getHiddenTablesForDiagram: () => [],
|
||||
setHiddenTablesForDiagram: emptyFn,
|
||||
hideTableForDiagram: emptyFn,
|
||||
unhideTableForDiagram: emptyFn,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ConfigContext } from './config-context';
|
||||
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
@@ -8,7 +8,7 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { getConfig, updateConfig: updateDataConfig } = useStorage();
|
||||
const [config, setConfig] = React.useState<ChartDBConfig | undefined>();
|
||||
const [config, setConfig] = useState<ChartDBConfig | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const loadConfig = async () => {
|
||||
@@ -44,84 +44,11 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
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 (
|
||||
<ConfigContext.Provider
|
||||
value={{
|
||||
config,
|
||||
updateConfig,
|
||||
getHiddenTablesForDiagram,
|
||||
setHiddenTablesForDiagram,
|
||||
hideTableForDiagram,
|
||||
unhideTableForDiagram,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { DBSchema } from '@/lib/domain';
|
||||
import type {
|
||||
DiagramFilter,
|
||||
FilterTableInfo,
|
||||
} from '@/lib/domain/diagram-filter/diagram-filter';
|
||||
import { emptyFn } from '@/lib/utils';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface DiagramFilterContext {
|
||||
filter?: DiagramFilter;
|
||||
loading: boolean;
|
||||
|
||||
hasActiveFilter: boolean;
|
||||
schemasDisplayed: DBSchema[];
|
||||
|
||||
clearSchemaIdsFilter: () => void;
|
||||
clearTableIdsFilter: () => void;
|
||||
|
||||
setTableIdsFilterEmpty: () => void;
|
||||
|
||||
// reset
|
||||
resetFilter: () => void;
|
||||
|
||||
toggleSchemaFilter: (schemaId: string) => void;
|
||||
toggleTableFilter: (tableId: string) => void;
|
||||
addSchemaToFilter: (schemaId: string) => void;
|
||||
addTablesToFilter: (attrs: {
|
||||
tableIds?: string[];
|
||||
filterCallback?: (table: FilterTableInfo) => boolean;
|
||||
}) => void;
|
||||
removeTablesFromFilter: (attrs: {
|
||||
tableIds?: string[];
|
||||
filterCallback?: (table: FilterTableInfo) => boolean;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export const diagramFilterContext = createContext<DiagramFilterContext>({
|
||||
hasActiveFilter: false,
|
||||
clearSchemaIdsFilter: emptyFn,
|
||||
clearTableIdsFilter: emptyFn,
|
||||
setTableIdsFilterEmpty: emptyFn,
|
||||
resetFilter: emptyFn,
|
||||
toggleSchemaFilter: emptyFn,
|
||||
toggleTableFilter: emptyFn,
|
||||
addSchemaToFilter: emptyFn,
|
||||
schemasDisplayed: [],
|
||||
addTablesToFilter: emptyFn,
|
||||
removeTablesFromFilter: emptyFn,
|
||||
loading: false,
|
||||
});
|
||||
559
src/context/diagram-filter-context/diagram-filter-provider.tsx
Normal file
559
src/context/diagram-filter-context/diagram-filter-provider.tsx
Normal file
@@ -0,0 +1,559 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { DiagramFilterContext } from './diagram-filter-context';
|
||||
import { diagramFilterContext } from './diagram-filter-context';
|
||||
import type {
|
||||
DiagramFilter,
|
||||
FilterTableInfo,
|
||||
} from '@/lib/domain/diagram-filter/diagram-filter';
|
||||
import {
|
||||
reduceFilter,
|
||||
spreadFilterTables,
|
||||
} from '@/lib/domain/diagram-filter/diagram-filter';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||
import { databasesWithSchemas, schemaNameToSchemaId } from '@/lib/domain';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import type { ChartDBEvent } from '../chartdb-context/chartdb-context';
|
||||
|
||||
export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { diagramId, tables, schemas, databaseType, events } = useChartDB();
|
||||
const { getDiagramFilter, updateDiagramFilter } = useStorage();
|
||||
const [filter, setFilter] = useState<DiagramFilter>({});
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
const allSchemasIds = useMemo(() => {
|
||||
return schemas.map((schema) => schema.id);
|
||||
}, [schemas]);
|
||||
|
||||
const allTables: FilterTableInfo[] = useMemo(() => {
|
||||
return tables.map(
|
||||
(table) =>
|
||||
({
|
||||
id: table.id,
|
||||
schemaId: table.schema
|
||||
? schemaNameToSchemaId(table.schema)
|
||||
: defaultSchemas[databaseType],
|
||||
schema: table.schema ?? defaultSchemas[databaseType],
|
||||
areaId: table.parentAreaId ?? undefined,
|
||||
}) satisfies FilterTableInfo
|
||||
);
|
||||
}, [tables, databaseType]);
|
||||
|
||||
const diagramIdOfLoadedFilter = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (diagramId && diagramId === diagramIdOfLoadedFilter.current) {
|
||||
updateDiagramFilter(diagramId, filter);
|
||||
}
|
||||
}, [diagramId, filter, updateDiagramFilter]);
|
||||
|
||||
// Reset filter when diagram changes
|
||||
useEffect(() => {
|
||||
if (diagramIdOfLoadedFilter.current === diagramId) {
|
||||
// If the diagramId hasn't changed, do not reset the filter
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const loadFilterFromStorage = async (diagramId: string) => {
|
||||
if (diagramId) {
|
||||
const storedFilter = await getDiagramFilter(diagramId);
|
||||
|
||||
let filterToSet = storedFilter;
|
||||
|
||||
if (!filterToSet) {
|
||||
// If no filter is stored, set default based on database type
|
||||
filterToSet =
|
||||
schemas.length > 1
|
||||
? { schemaIds: [schemas[0].id] }
|
||||
: {};
|
||||
}
|
||||
|
||||
setFilter(filterToSet);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
setFilter({});
|
||||
|
||||
if (diagramId) {
|
||||
loadFilterFromStorage(diagramId);
|
||||
diagramIdOfLoadedFilter.current = diagramId;
|
||||
}
|
||||
}, [diagramId, getDiagramFilter, schemas]);
|
||||
|
||||
const clearSchemaIds: DiagramFilterContext['clearSchemaIdsFilter'] =
|
||||
useCallback(() => {
|
||||
setFilter(
|
||||
(prev) =>
|
||||
({
|
||||
...prev,
|
||||
schemaIds: undefined,
|
||||
}) satisfies DiagramFilter
|
||||
);
|
||||
}, []);
|
||||
|
||||
const clearTableIds: DiagramFilterContext['clearTableIdsFilter'] =
|
||||
useCallback(() => {
|
||||
setFilter(
|
||||
(prev) =>
|
||||
({
|
||||
...prev,
|
||||
tableIds: undefined,
|
||||
}) satisfies DiagramFilter
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setTableIdsEmpty: DiagramFilterContext['setTableIdsFilterEmpty'] =
|
||||
useCallback(() => {
|
||||
setFilter(
|
||||
(prev) =>
|
||||
({
|
||||
...prev,
|
||||
tableIds: [],
|
||||
}) satisfies DiagramFilter
|
||||
);
|
||||
}, []);
|
||||
|
||||
// Reset filter
|
||||
const resetFilter: DiagramFilterContext['resetFilter'] = useCallback(() => {
|
||||
setFilter({});
|
||||
}, []);
|
||||
|
||||
const toggleSchemaFilter: DiagramFilterContext['toggleSchemaFilter'] =
|
||||
useCallback(
|
||||
(schemaId: string) => {
|
||||
setFilter((prev) => {
|
||||
const currentSchemaIds = prev.schemaIds;
|
||||
|
||||
// Check if schema is currently visible
|
||||
const isSchemaVisible = !allTables.some(
|
||||
(table) =>
|
||||
table.schemaId === schemaId &&
|
||||
filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter: prev,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
}) === false
|
||||
);
|
||||
|
||||
let newSchemaIds: string[] | undefined;
|
||||
let newTableIds: string[] | undefined = prev.tableIds;
|
||||
|
||||
if (isSchemaVisible) {
|
||||
// Schema is visible, make it not visible
|
||||
if (!currentSchemaIds) {
|
||||
// All schemas are visible, create filter with all except this one
|
||||
newSchemaIds = allSchemasIds.filter(
|
||||
(id) => id !== schemaId
|
||||
);
|
||||
} else {
|
||||
// Remove this schema from the filter
|
||||
newSchemaIds = currentSchemaIds.filter(
|
||||
(id) => id !== schemaId
|
||||
);
|
||||
}
|
||||
|
||||
// Remove tables from this schema from tableIds if present
|
||||
if (prev.tableIds) {
|
||||
const schemaTableIds = allTables
|
||||
.filter((table) => table.schemaId === schemaId)
|
||||
.map((table) => table.id);
|
||||
newTableIds = prev.tableIds.filter(
|
||||
(id) => !schemaTableIds.includes(id)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Schema is not visible, make it visible
|
||||
newSchemaIds = [
|
||||
...new Set([...(currentSchemaIds || []), schemaId]),
|
||||
];
|
||||
|
||||
// Add tables from this schema to tableIds if tableIds is defined
|
||||
if (prev.tableIds) {
|
||||
const schemaTableIds = allTables
|
||||
.filter((table) => table.schemaId === schemaId)
|
||||
.map((table) => table.id);
|
||||
newTableIds = [
|
||||
...new Set([
|
||||
...prev.tableIds,
|
||||
...schemaTableIds,
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Use reduceFilter to optimize and handle edge cases
|
||||
return reduceFilter(
|
||||
{
|
||||
schemaIds: newSchemaIds,
|
||||
tableIds: newTableIds,
|
||||
},
|
||||
allTables satisfies FilterTableInfo[],
|
||||
{
|
||||
databaseWithSchemas:
|
||||
databasesWithSchemas.includes(databaseType),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
[allSchemasIds, allTables, databaseType]
|
||||
);
|
||||
|
||||
const toggleTableFilterForNoSchema = useCallback(
|
||||
(tableId: string) => {
|
||||
setFilter((prev) => {
|
||||
const currentTableIds = prev.tableIds;
|
||||
|
||||
// Check if table is currently visible
|
||||
const isTableVisible = filterTable({
|
||||
table: { id: tableId, schema: undefined },
|
||||
filter: prev,
|
||||
options: { defaultSchema: undefined },
|
||||
});
|
||||
|
||||
let newTableIds: string[] | undefined;
|
||||
|
||||
if (isTableVisible) {
|
||||
// Table is visible, make it not visible
|
||||
if (!currentTableIds) {
|
||||
// All tables are visible, create filter with all except this one
|
||||
newTableIds = allTables
|
||||
.filter((t) => t.id !== tableId)
|
||||
.map((t) => t.id);
|
||||
} else {
|
||||
// Remove this table from the filter
|
||||
newTableIds = currentTableIds.filter(
|
||||
(id) => id !== tableId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Table is not visible, make it visible
|
||||
newTableIds = [
|
||||
...new Set([...(currentTableIds || []), tableId]),
|
||||
];
|
||||
}
|
||||
|
||||
// Use reduceFilter to optimize and handle edge cases
|
||||
return reduceFilter(
|
||||
{
|
||||
schemaIds: undefined,
|
||||
tableIds: newTableIds,
|
||||
},
|
||||
allTables satisfies FilterTableInfo[],
|
||||
{
|
||||
databaseWithSchemas:
|
||||
databasesWithSchemas.includes(databaseType),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
[allTables, databaseType]
|
||||
);
|
||||
|
||||
const toggleTableFilter: DiagramFilterContext['toggleTableFilter'] =
|
||||
useCallback(
|
||||
(tableId: string) => {
|
||||
if (!databasesWithSchemas.includes(databaseType)) {
|
||||
// No schemas, toggle table filter without schema context
|
||||
toggleTableFilterForNoSchema(tableId);
|
||||
return;
|
||||
}
|
||||
|
||||
setFilter((prev) => {
|
||||
// Find the table in the tables list
|
||||
const tableInfo = allTables.find((t) => t.id === tableId);
|
||||
|
||||
if (!tableInfo) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// Check if table is currently visible using filterTable
|
||||
const isTableVisible = filterTable({
|
||||
table: {
|
||||
id: tableInfo.id,
|
||||
schema: tableInfo.schema,
|
||||
},
|
||||
filter: prev,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
});
|
||||
|
||||
let newSchemaIds = prev.schemaIds;
|
||||
let newTableIds = prev.tableIds;
|
||||
|
||||
if (isTableVisible) {
|
||||
// Table is visible, make it not visible
|
||||
|
||||
// If the table is visible due to its schema being in schemaIds
|
||||
if (
|
||||
tableInfo?.schemaId &&
|
||||
prev.schemaIds?.includes(tableInfo.schemaId)
|
||||
) {
|
||||
// Remove the schema from schemaIds and add all other tables from that schema to tableIds
|
||||
newSchemaIds = prev.schemaIds.filter(
|
||||
(id) => id !== tableInfo.schemaId
|
||||
);
|
||||
|
||||
// Get all other tables from this schema (except the one being toggled)
|
||||
const otherTablesFromSchema = allTables
|
||||
.filter(
|
||||
(t) =>
|
||||
t.schemaId === tableInfo.schemaId &&
|
||||
t.id !== tableId
|
||||
)
|
||||
.map((t) => t.id);
|
||||
|
||||
// Add these tables to tableIds
|
||||
newTableIds = [
|
||||
...(prev.tableIds || []),
|
||||
...otherTablesFromSchema,
|
||||
];
|
||||
} else if (prev.tableIds?.includes(tableId)) {
|
||||
// Table is visible because it's in tableIds, remove it
|
||||
newTableIds = prev.tableIds.filter(
|
||||
(id) => id !== tableId
|
||||
);
|
||||
} else if (!prev.tableIds && !prev.schemaIds) {
|
||||
// No filters = all visible, create filter with all tables except this one
|
||||
newTableIds = allTables
|
||||
.filter((t) => t.id !== tableId)
|
||||
.map((t) => t.id);
|
||||
}
|
||||
} else {
|
||||
// Table is not visible, make it visible by adding to tableIds
|
||||
newTableIds = [...(prev.tableIds || []), tableId];
|
||||
}
|
||||
|
||||
// Use reduceFilter to optimize and handle edge cases
|
||||
return reduceFilter(
|
||||
{
|
||||
schemaIds: newSchemaIds,
|
||||
tableIds: newTableIds,
|
||||
},
|
||||
allTables satisfies FilterTableInfo[],
|
||||
{
|
||||
databaseWithSchemas:
|
||||
databasesWithSchemas.includes(databaseType),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
[allTables, databaseType, toggleTableFilterForNoSchema]
|
||||
);
|
||||
|
||||
const addSchemaToFilter: DiagramFilterContext['addSchemaToFilter'] =
|
||||
useCallback(
|
||||
(schemaId: string) => {
|
||||
setFilter((prev) => {
|
||||
const currentSchemaIds = prev.schemaIds;
|
||||
if (!currentSchemaIds) {
|
||||
// No schemas are filtered
|
||||
return prev;
|
||||
}
|
||||
|
||||
// If schema is already filtered, do nothing
|
||||
if (currentSchemaIds.includes(schemaId)) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// Add schema to the filter
|
||||
const newSchemaIds = [...currentSchemaIds, schemaId];
|
||||
|
||||
if (newSchemaIds.length === allSchemasIds.length) {
|
||||
// All schemas are now filtered, set to undefined
|
||||
return {
|
||||
...prev,
|
||||
schemaIds: undefined,
|
||||
} satisfies DiagramFilter;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
schemaIds: newSchemaIds,
|
||||
} satisfies DiagramFilter;
|
||||
});
|
||||
},
|
||||
[allSchemasIds.length]
|
||||
);
|
||||
|
||||
const hasActiveFilter: boolean = useMemo(() => {
|
||||
return !!filter.schemaIds || !!filter.tableIds;
|
||||
}, [filter]);
|
||||
|
||||
const schemasDisplayed: DiagramFilterContext['schemasDisplayed'] =
|
||||
useMemo(() => {
|
||||
if (!hasActiveFilter) {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
const displayedSchemaIds = new Set<string>();
|
||||
for (const table of allTables) {
|
||||
if (
|
||||
filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
})
|
||||
) {
|
||||
if (table.schemaId) {
|
||||
displayedSchemaIds.add(table.schemaId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return schemas.filter((schema) =>
|
||||
displayedSchemaIds.has(schema.id)
|
||||
);
|
||||
}, [hasActiveFilter, schemas, allTables, filter, databaseType]);
|
||||
|
||||
const addTablesToFilter: DiagramFilterContext['addTablesToFilter'] =
|
||||
useCallback(
|
||||
({ tableIds, filterCallback }) => {
|
||||
setFilter((prev) => {
|
||||
let tableIdsToAdd: string[];
|
||||
|
||||
if (tableIds) {
|
||||
// If tableIds are provided, use them directly
|
||||
tableIdsToAdd = tableIds;
|
||||
} else if (filterCallback) {
|
||||
// If filterCallback is provided, filter tables based on it
|
||||
tableIdsToAdd = allTables
|
||||
.filter(filterCallback)
|
||||
.map((table) => table.id);
|
||||
} else {
|
||||
// If neither is provided, do nothing
|
||||
return prev;
|
||||
}
|
||||
|
||||
const filterByTableIds = spreadFilterTables(
|
||||
prev,
|
||||
allTables satisfies FilterTableInfo[]
|
||||
);
|
||||
|
||||
const currentTableIds = filterByTableIds.tableIds || [];
|
||||
const newTableIds = [
|
||||
...new Set([...currentTableIds, ...tableIdsToAdd]),
|
||||
];
|
||||
|
||||
return reduceFilter(
|
||||
{
|
||||
...filterByTableIds,
|
||||
tableIds: newTableIds,
|
||||
},
|
||||
allTables satisfies FilterTableInfo[],
|
||||
{
|
||||
databaseWithSchemas:
|
||||
databasesWithSchemas.includes(databaseType),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
[allTables, databaseType]
|
||||
);
|
||||
|
||||
const removeTablesFromFilter: DiagramFilterContext['removeTablesFromFilter'] =
|
||||
useCallback(
|
||||
({ tableIds, filterCallback }) => {
|
||||
setFilter((prev) => {
|
||||
let tableIdsToRemovoe: string[];
|
||||
|
||||
if (tableIds) {
|
||||
// If tableIds are provided, use them directly
|
||||
tableIdsToRemovoe = tableIds;
|
||||
} else if (filterCallback) {
|
||||
// If filterCallback is provided, filter tables based on it
|
||||
tableIdsToRemovoe = allTables
|
||||
.filter(filterCallback)
|
||||
.map((table) => table.id);
|
||||
} else {
|
||||
// If neither is provided, do nothing
|
||||
return prev;
|
||||
}
|
||||
|
||||
const filterByTableIds = spreadFilterTables(
|
||||
prev,
|
||||
allTables satisfies FilterTableInfo[]
|
||||
);
|
||||
|
||||
const currentTableIds = filterByTableIds.tableIds || [];
|
||||
const newTableIds = currentTableIds.filter(
|
||||
(id) => !tableIdsToRemovoe.includes(id)
|
||||
);
|
||||
|
||||
return reduceFilter(
|
||||
{
|
||||
...filterByTableIds,
|
||||
tableIds: newTableIds,
|
||||
},
|
||||
allTables satisfies FilterTableInfo[],
|
||||
{
|
||||
databaseWithSchemas:
|
||||
databasesWithSchemas.includes(databaseType),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
[allTables, databaseType]
|
||||
);
|
||||
|
||||
const eventConsumer = useCallback(
|
||||
(event: ChartDBEvent) => {
|
||||
if (!hasActiveFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.action === 'add_tables') {
|
||||
addTablesToFilter({
|
||||
tableIds: event.data.tables.map((table) => table.id),
|
||||
});
|
||||
}
|
||||
},
|
||||
[hasActiveFilter, addTablesToFilter]
|
||||
);
|
||||
|
||||
events.useSubscription(eventConsumer);
|
||||
|
||||
const value: DiagramFilterContext = {
|
||||
loading,
|
||||
filter,
|
||||
clearSchemaIdsFilter: clearSchemaIds,
|
||||
setTableIdsFilterEmpty: setTableIdsEmpty,
|
||||
clearTableIdsFilter: clearTableIds,
|
||||
resetFilter,
|
||||
toggleSchemaFilter,
|
||||
toggleTableFilter,
|
||||
addSchemaToFilter,
|
||||
hasActiveFilter,
|
||||
schemasDisplayed,
|
||||
addTablesToFilter,
|
||||
removeTablesFromFilter,
|
||||
};
|
||||
|
||||
return (
|
||||
<diagramFilterContext.Provider value={value}>
|
||||
{children}
|
||||
</diagramFilterContext.Provider>
|
||||
);
|
||||
};
|
||||
4
src/context/diagram-filter-context/use-diagram-filter.ts
Normal file
4
src/context/diagram-filter-context/use-diagram-filter.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { diagramFilterContext } from './diagram-filter-context';
|
||||
|
||||
export const useDiagramFilter = () => useContext(diagramFilterContext);
|
||||
@@ -32,14 +32,20 @@ export interface DiffContext {
|
||||
originalDiagram: Diagram | null;
|
||||
diffMap: DiffMap;
|
||||
hasDiff: boolean;
|
||||
isSummaryOnly: boolean;
|
||||
|
||||
calculateDiff: ({
|
||||
diagram,
|
||||
newDiagram,
|
||||
options,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
options?: {
|
||||
summaryOnly?: boolean;
|
||||
};
|
||||
}) => void;
|
||||
resetDiff: () => void;
|
||||
|
||||
// table diff
|
||||
checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean;
|
||||
@@ -60,6 +66,15 @@ export interface DiffContext {
|
||||
checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean;
|
||||
getFieldNewName: ({ fieldId }: { fieldId: string }) => string | null;
|
||||
getFieldNewType: ({ fieldId }: { fieldId: string }) => DataType | null;
|
||||
getFieldNewPrimaryKey: ({ fieldId }: { fieldId: string }) => boolean | null;
|
||||
getFieldNewNullable: ({ fieldId }: { fieldId: string }) => boolean | null;
|
||||
getFieldNewCharacterMaximumLength: ({
|
||||
fieldId,
|
||||
}: {
|
||||
fieldId: string;
|
||||
}) => string | null;
|
||||
getFieldNewScale: ({ fieldId }: { fieldId: string }) => number | null;
|
||||
getFieldNewPrecision: ({ fieldId }: { fieldId: string }) => number | null;
|
||||
|
||||
// relationship diff
|
||||
checkIfNewRelationship: ({
|
||||
|
||||
@@ -32,6 +32,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||
const [fieldsChanged, setFieldsChanged] = React.useState<
|
||||
Map<string, boolean>
|
||||
>(new Map<string, boolean>());
|
||||
const [isSummaryOnly, setIsSummaryOnly] = React.useState<boolean>(false);
|
||||
|
||||
const events = useEventEmitter<DiffEvent>();
|
||||
|
||||
@@ -127,7 +128,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||
);
|
||||
|
||||
const calculateDiff: DiffContext['calculateDiff'] = useCallback(
|
||||
({ diagram, newDiagram: newDiagramArg }) => {
|
||||
({ diagram, newDiagram: newDiagramArg, options }) => {
|
||||
const {
|
||||
diffMap: newDiffs,
|
||||
changedTables: newChangedTables,
|
||||
@@ -139,6 +140,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setFieldsChanged(newChangedFields);
|
||||
setNewDiagram(newDiagramArg);
|
||||
setOriginalDiagram(diagram);
|
||||
setIsSummaryOnly(options?.summaryOnly ?? false);
|
||||
|
||||
events.emit({
|
||||
action: 'diff_calculated',
|
||||
@@ -305,6 +307,117 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[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 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 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 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 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 diff.newValue as number;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[diffMap]
|
||||
);
|
||||
|
||||
const checkIfNewRelationship = useCallback<
|
||||
DiffContext['checkIfNewRelationship']
|
||||
>(
|
||||
@@ -339,6 +452,15 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[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 (
|
||||
<diffContext.Provider
|
||||
value={{
|
||||
@@ -346,8 +468,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||
originalDiagram,
|
||||
diffMap,
|
||||
hasDiff: diffMap.size > 0,
|
||||
isSummaryOnly,
|
||||
|
||||
calculateDiff,
|
||||
resetDiff,
|
||||
|
||||
// table diff
|
||||
getTableNewName,
|
||||
@@ -362,6 +486,11 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||
checkIfNewField,
|
||||
getFieldNewName,
|
||||
getFieldNewType,
|
||||
getFieldNewPrimaryKey,
|
||||
getFieldNewNullable,
|
||||
getFieldNewCharacterMaximumLength,
|
||||
getFieldNewScale,
|
||||
getFieldNewPrecision,
|
||||
|
||||
// relationship diff
|
||||
checkIfNewRelationship,
|
||||
|
||||
@@ -2,9 +2,9 @@ import { emptyFn } from '@/lib/utils';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type SidebarSection =
|
||||
| 'dbml'
|
||||
| 'tables'
|
||||
| 'relationships'
|
||||
| 'dependencies'
|
||||
| 'refs'
|
||||
| 'areas'
|
||||
| 'customTypes';
|
||||
|
||||
@@ -13,14 +13,16 @@ export interface LayoutContext {
|
||||
openTableFromSidebar: (tableId: string) => void;
|
||||
closeAllTablesInSidebar: () => void;
|
||||
|
||||
openedRelationshipInSidebar: string | undefined;
|
||||
openRelationshipFromSidebar: (relationshipId: string) => void;
|
||||
closeAllRelationshipsInSidebar: () => void;
|
||||
|
||||
openedDependencyInSidebar: string | undefined;
|
||||
openDependencyFromSidebar: (dependencyId: string) => void;
|
||||
closeAllDependenciesInSidebar: () => void;
|
||||
|
||||
openedRefInSidebar: string | undefined;
|
||||
openRefFromSidebar: (refId: string) => void;
|
||||
closeAllRefsInSidebar: () => void;
|
||||
|
||||
openedAreaInSidebar: string | undefined;
|
||||
openAreaFromSidebar: (areaId: string) => void;
|
||||
closeAllAreasInSidebar: () => void;
|
||||
@@ -36,24 +38,22 @@ export interface LayoutContext {
|
||||
hideSidePanel: () => void;
|
||||
showSidePanel: () => void;
|
||||
toggleSidePanel: () => void;
|
||||
|
||||
isSelectSchemaOpen: boolean;
|
||||
openSelectSchema: () => void;
|
||||
closeSelectSchema: () => void;
|
||||
}
|
||||
|
||||
export const layoutContext = createContext<LayoutContext>({
|
||||
openedTableInSidebar: undefined,
|
||||
selectedSidebarSection: 'tables',
|
||||
|
||||
openedRelationshipInSidebar: undefined,
|
||||
openRelationshipFromSidebar: emptyFn,
|
||||
closeAllRelationshipsInSidebar: emptyFn,
|
||||
|
||||
openedDependencyInSidebar: undefined,
|
||||
openDependencyFromSidebar: emptyFn,
|
||||
closeAllDependenciesInSidebar: emptyFn,
|
||||
|
||||
openedRefInSidebar: undefined,
|
||||
openRefFromSidebar: emptyFn,
|
||||
closeAllRefsInSidebar: emptyFn,
|
||||
|
||||
openedAreaInSidebar: undefined,
|
||||
openAreaFromSidebar: emptyFn,
|
||||
closeAllAreasInSidebar: emptyFn,
|
||||
@@ -70,8 +70,4 @@ export const layoutContext = createContext<LayoutContext>({
|
||||
hideSidePanel: emptyFn,
|
||||
showSidePanel: emptyFn,
|
||||
toggleSidePanel: emptyFn,
|
||||
|
||||
isSelectSchemaOpen: false,
|
||||
openSelectSchema: emptyFn,
|
||||
closeSelectSchema: emptyFn,
|
||||
});
|
||||
|
||||
@@ -10,10 +10,9 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [openedRelationshipInSidebar, setOpenedRelationshipInSidebar] =
|
||||
React.useState<string | undefined>();
|
||||
const [openedDependencyInSidebar, setOpenedDependencyInSidebar] =
|
||||
React.useState<string | undefined>();
|
||||
const [openedRefInSidebar, setOpenedRefInSidebar] = React.useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState<
|
||||
string | undefined
|
||||
>();
|
||||
@@ -23,17 +22,18 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
React.useState<SidebarSection>('tables');
|
||||
const [isSidePanelShowed, setIsSidePanelShowed] =
|
||||
React.useState<boolean>(isDesktop);
|
||||
const [isSelectSchemaOpen, setIsSelectSchemaOpen] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
||||
() => setOpenedTableInSidebar('');
|
||||
|
||||
const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] =
|
||||
() => setOpenedRelationshipInSidebar('');
|
||||
() => setOpenedRefInSidebar('');
|
||||
|
||||
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
|
||||
() => setOpenedDependencyInSidebar('');
|
||||
() => setOpenedRefInSidebar('');
|
||||
|
||||
const closeAllRefsInSidebar: LayoutContext['closeAllRefsInSidebar'] = () =>
|
||||
setOpenedRefInSidebar('');
|
||||
|
||||
const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] =
|
||||
() => setOpenedAreaInSidebar('');
|
||||
@@ -62,17 +62,23 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] =
|
||||
(relationshipId) => {
|
||||
showSidePanel();
|
||||
setSelectedSidebarSection('relationships');
|
||||
setOpenedRelationshipInSidebar(relationshipId);
|
||||
setSelectedSidebarSection('refs');
|
||||
setOpenedRefInSidebar(relationshipId);
|
||||
};
|
||||
|
||||
const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] =
|
||||
(dependencyId) => {
|
||||
showSidePanel();
|
||||
setSelectedSidebarSection('dependencies');
|
||||
setOpenedDependencyInSidebar(dependencyId);
|
||||
setSelectedSidebarSection('refs');
|
||||
setOpenedRefInSidebar(dependencyId);
|
||||
};
|
||||
|
||||
const openRefFromSidebar: LayoutContext['openRefFromSidebar'] = (refId) => {
|
||||
showSidePanel();
|
||||
setSelectedSidebarSection('refs');
|
||||
setOpenedRefInSidebar(refId);
|
||||
};
|
||||
|
||||
const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = (
|
||||
areaId
|
||||
) => {
|
||||
@@ -88,11 +94,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setOpenedTableInSidebar(customTypeId);
|
||||
};
|
||||
|
||||
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
|
||||
setIsSelectSchemaOpen(true);
|
||||
|
||||
const closeSelectSchema: LayoutContext['closeSelectSchema'] = () =>
|
||||
setIsSelectSchemaOpen(false);
|
||||
return (
|
||||
<layoutContext.Provider
|
||||
value={{
|
||||
@@ -100,7 +101,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
selectedSidebarSection,
|
||||
openTableFromSidebar,
|
||||
selectSidebarSection: setSelectedSidebarSection,
|
||||
openedRelationshipInSidebar,
|
||||
openRelationshipFromSidebar,
|
||||
closeAllTablesInSidebar,
|
||||
closeAllRelationshipsInSidebar,
|
||||
@@ -108,12 +108,11 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
hideSidePanel,
|
||||
showSidePanel,
|
||||
toggleSidePanel,
|
||||
isSelectSchemaOpen,
|
||||
openSelectSchema,
|
||||
closeSelectSchema,
|
||||
openedDependencyInSidebar,
|
||||
openDependencyFromSidebar,
|
||||
closeAllDependenciesInSidebar,
|
||||
openedRefInSidebar,
|
||||
openRefFromSidebar,
|
||||
closeAllRefsInSidebar,
|
||||
openedAreaInSidebar,
|
||||
openAreaFromSidebar,
|
||||
closeAllAreasInSidebar,
|
||||
|
||||
@@ -4,8 +4,6 @@ import type { Theme } from '../theme-context/theme-context';
|
||||
|
||||
export type ScrollAction = 'pan' | 'zoom';
|
||||
|
||||
export type SchemasFilter = Record<string, string[]>;
|
||||
|
||||
export interface LocalConfigContext {
|
||||
theme: Theme;
|
||||
setTheme: (theme: Theme) => void;
|
||||
@@ -13,16 +11,14 @@ export interface LocalConfigContext {
|
||||
scrollAction: ScrollAction;
|
||||
setScrollAction: (action: ScrollAction) => void;
|
||||
|
||||
schemasFilter: SchemasFilter;
|
||||
setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>;
|
||||
showDBViews: boolean;
|
||||
setShowDBViews: (showViews: boolean) => void;
|
||||
|
||||
showCardinality: boolean;
|
||||
setShowCardinality: (showCardinality: boolean) => void;
|
||||
|
||||
hideMultiSchemaNotification: boolean;
|
||||
setHideMultiSchemaNotification: (
|
||||
hideMultiSchemaNotification: boolean
|
||||
) => void;
|
||||
showFieldAttributes: boolean;
|
||||
setShowFieldAttributes: (showFieldAttributes: boolean) => void;
|
||||
|
||||
githubRepoOpened: boolean;
|
||||
setGithubRepoOpened: (githubRepoOpened: boolean) => void;
|
||||
@@ -30,9 +26,6 @@ export interface LocalConfigContext {
|
||||
starUsDialogLastOpen: number;
|
||||
setStarUsDialogLastOpen: (lastOpen: number) => void;
|
||||
|
||||
showDependenciesOnCanvas: boolean;
|
||||
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
|
||||
|
||||
showMiniMapOnCanvas: boolean;
|
||||
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
|
||||
}
|
||||
@@ -44,14 +37,14 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
||||
scrollAction: 'pan',
|
||||
setScrollAction: emptyFn,
|
||||
|
||||
schemasFilter: {},
|
||||
setSchemasFilter: emptyFn,
|
||||
showDBViews: false,
|
||||
setShowDBViews: emptyFn,
|
||||
|
||||
showCardinality: true,
|
||||
setShowCardinality: emptyFn,
|
||||
|
||||
hideMultiSchemaNotification: false,
|
||||
setHideMultiSchemaNotification: emptyFn,
|
||||
showFieldAttributes: true,
|
||||
setShowFieldAttributes: emptyFn,
|
||||
|
||||
githubRepoOpened: false,
|
||||
setGithubRepoOpened: emptyFn,
|
||||
@@ -59,9 +52,6 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
||||
starUsDialogLastOpen: 0,
|
||||
setStarUsDialogLastOpen: emptyFn,
|
||||
|
||||
showDependenciesOnCanvas: false,
|
||||
setShowDependenciesOnCanvas: emptyFn,
|
||||
|
||||
showMiniMapOnCanvas: false,
|
||||
setShowMiniMapOnCanvas: emptyFn,
|
||||
});
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import type { SchemasFilter, ScrollAction } from './local-config-context';
|
||||
import type { ScrollAction } from './local-config-context';
|
||||
import { LocalConfigContext } from './local-config-context';
|
||||
import type { Theme } from '../theme-context/theme-context';
|
||||
|
||||
const themeKey = 'theme';
|
||||
const scrollActionKey = 'scroll_action';
|
||||
const schemasFilterKey = 'schemas_filter';
|
||||
const showCardinalityKey = 'show_cardinality';
|
||||
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
|
||||
const showFieldAttributesKey = 'show_field_attributes';
|
||||
const githubRepoOpenedKey = 'github_repo_opened';
|
||||
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
||||
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
|
||||
const showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
|
||||
const showDBViewsKey = 'show_db_views';
|
||||
|
||||
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
@@ -24,20 +23,17 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
|
||||
);
|
||||
|
||||
const [schemasFilter, setSchemasFilter] = React.useState<SchemasFilter>(
|
||||
JSON.parse(
|
||||
localStorage.getItem(schemasFilterKey) || '{}'
|
||||
) as SchemasFilter
|
||||
const [showDBViews, setShowDBViews] = React.useState<boolean>(
|
||||
(localStorage.getItem(showDBViewsKey) || 'false') === 'true'
|
||||
);
|
||||
|
||||
const [showCardinality, setShowCardinality] = React.useState<boolean>(
|
||||
(localStorage.getItem(showCardinalityKey) || 'true') === 'true'
|
||||
);
|
||||
|
||||
const [hideMultiSchemaNotification, setHideMultiSchemaNotification] =
|
||||
const [showFieldAttributes, setShowFieldAttributes] =
|
||||
React.useState<boolean>(
|
||||
(localStorage.getItem(hideMultiSchemaNotificationKey) ||
|
||||
'false') === 'true'
|
||||
(localStorage.getItem(showFieldAttributesKey) || 'true') === 'true'
|
||||
);
|
||||
|
||||
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
|
||||
@@ -49,12 +45,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
|
||||
);
|
||||
|
||||
const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] =
|
||||
React.useState<boolean>(
|
||||
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
|
||||
'true'
|
||||
);
|
||||
|
||||
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
|
||||
React.useState<boolean>(
|
||||
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
|
||||
@@ -71,13 +61,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
||||
}, [githubRepoOpened]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(
|
||||
hideMultiSchemaNotificationKey,
|
||||
hideMultiSchemaNotification.toString()
|
||||
);
|
||||
}, [hideMultiSchemaNotification]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(themeKey, theme);
|
||||
}, [theme]);
|
||||
@@ -87,20 +70,13 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
}, [scrollAction]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(schemasFilterKey, JSON.stringify(schemasFilter));
|
||||
}, [schemasFilter]);
|
||||
localStorage.setItem(showDBViewsKey, showDBViews.toString());
|
||||
}, [showDBViews]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(showCardinalityKey, showCardinality.toString());
|
||||
}, [showCardinality]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(
|
||||
showDependenciesOnCanvasKey,
|
||||
showDependenciesOnCanvas.toString()
|
||||
);
|
||||
}, [showDependenciesOnCanvas]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(
|
||||
showMiniMapOnCanvasKey,
|
||||
@@ -115,18 +91,16 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setTheme,
|
||||
scrollAction,
|
||||
setScrollAction,
|
||||
schemasFilter,
|
||||
setSchemasFilter,
|
||||
showDBViews,
|
||||
setShowDBViews,
|
||||
showCardinality,
|
||||
setShowCardinality,
|
||||
hideMultiSchemaNotification,
|
||||
setHideMultiSchemaNotification,
|
||||
showFieldAttributes,
|
||||
setShowFieldAttributes,
|
||||
setGithubRepoOpened,
|
||||
githubRepoOpened,
|
||||
starUsDialogLastOpen,
|
||||
setStarUsDialogLastOpen,
|
||||
showDependenciesOnCanvas,
|
||||
setShowDependenciesOnCanvas,
|
||||
showMiniMapOnCanvas,
|
||||
setShowMiniMapOnCanvas,
|
||||
}}
|
||||
|
||||
@@ -7,12 +7,21 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||
import type { Area } from '@/lib/domain/area';
|
||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||
|
||||
export interface StorageContext {
|
||||
// Config operations
|
||||
getConfig: () => Promise<ChartDBConfig | undefined>;
|
||||
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
|
||||
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
||||
listDiagrams: (options?: {
|
||||
@@ -132,6 +141,10 @@ export const storageInitialValue: StorageContext = {
|
||||
getConfig: emptyFn,
|
||||
updateConfig: emptyFn,
|
||||
|
||||
getDiagramFilter: emptyFn,
|
||||
updateDiagramFilter: emptyFn,
|
||||
deleteDiagramFilter: emptyFn,
|
||||
|
||||
addDiagram: emptyFn,
|
||||
listDiagrams: emptyFn,
|
||||
getDiagram: emptyFn,
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||
import type { Area } from '@/lib/domain/area';
|
||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||
|
||||
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
@@ -44,6 +45,10 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
ChartDBConfig & { id: number },
|
||||
'id' // primary key "id" (for the typings only)
|
||||
>;
|
||||
diagram_filters: EntityTable<
|
||||
DiagramFilter & { diagramId: string },
|
||||
'diagramId' // primary key "id" (for the typings only)
|
||||
>;
|
||||
};
|
||||
|
||||
// Schema declaration:
|
||||
@@ -190,6 +195,27 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
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.on('ready', async () => {
|
||||
const config = await dexieDB.config.get(1);
|
||||
|
||||
@@ -217,6 +243,34 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[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(
|
||||
async ({ diagramId, table }) => {
|
||||
await db.db_tables.add({
|
||||
@@ -756,6 +810,9 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
deleteCustomType,
|
||||
listCustomTypes,
|
||||
deleteDiagramCustomTypes,
|
||||
getDiagramFilter,
|
||||
updateDiagramFilter,
|
||||
deleteDiagramFilter,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -61,6 +61,7 @@ export const SelectTables: React.FC<SelectTablesProps> = ({
|
||||
const [showTables, setShowTables] = useState(true);
|
||||
const [showViews, setShowViews] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
|
||||
// Prepare all tables and views with their metadata
|
||||
const allTables = useMemo(() => {
|
||||
@@ -258,22 +259,37 @@ export const SelectTables: React.FC<SelectTablesProps> = ({
|
||||
setSelectedTables(new Set());
|
||||
}, []);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
const selectedTableObjects: SelectedTable[] = Array.from(selectedTables)
|
||||
.map((key): SelectedTable | null => {
|
||||
const table = allTables.find((t) => t.key === key);
|
||||
if (!table) return null;
|
||||
const handleConfirm = useCallback(async () => {
|
||||
if (isImporting) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
schema: table.schema,
|
||||
table: table.tableName,
|
||||
type: table.type,
|
||||
} satisfies SelectedTable;
|
||||
})
|
||||
.filter((t): t is SelectedTable => t !== null);
|
||||
setIsImporting(true);
|
||||
|
||||
onImport({ selectedTables: selectedTableObjects, databaseMetadata });
|
||||
}, [selectedTables, allTables, onImport, databaseMetadata]);
|
||||
try {
|
||||
const selectedTableObjects: SelectedTable[] = Array.from(
|
||||
selectedTables
|
||||
)
|
||||
.map((key): SelectedTable | null => {
|
||||
const table = allTables.find((t) => t.key === key);
|
||||
if (!table) return null;
|
||||
|
||||
return {
|
||||
schema: table.schema,
|
||||
table: table.tableName,
|
||||
type: table.type,
|
||||
} satisfies SelectedTable;
|
||||
})
|
||||
.filter((t): t is SelectedTable => t !== null);
|
||||
|
||||
await onImport({
|
||||
selectedTables: selectedTableObjects,
|
||||
databaseMetadata,
|
||||
});
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
}, [selectedTables, allTables, onImport, databaseMetadata, isImporting]);
|
||||
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
|
||||
@@ -635,27 +651,29 @@ export const SelectTables: React.FC<SelectTablesProps> = ({
|
||||
</div>
|
||||
{isDesktop ? renderPagination() : null}
|
||||
</DialogInternalContent>
|
||||
<DialogFooter
|
||||
// className={cn(
|
||||
// 'gap-2',
|
||||
// isDesktop
|
||||
// ? 'flex items-center justify-between'
|
||||
// : 'flex flex-col'
|
||||
// )}
|
||||
className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2 md:justify-between md:gap-0"
|
||||
>
|
||||
{/* Desktop layout */}
|
||||
|
||||
<Button type="button" variant="secondary" onClick={onBack}>
|
||||
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2 md:justify-between md:gap-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onBack}
|
||||
disabled={isImporting}
|
||||
>
|
||||
{t('new_diagram_dialog.back')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={selectedTables.size === 0}
|
||||
disabled={selectedTables.size === 0 || isImporting}
|
||||
className="bg-pink-500 text-white hover:bg-pink-600"
|
||||
>
|
||||
Import {selectedTables.size} Tables
|
||||
{isImporting ? (
|
||||
<>
|
||||
<Spinner className="mr-2 size-4 text-white" />
|
||||
Importing...
|
||||
</>
|
||||
) : (
|
||||
`Import ${selectedTables.size} Tables`
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{!isDesktop ? renderPagination() : null}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Dialog, DialogContent } from '@/components/dialog/dialog';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
||||
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||
|
||||
@@ -69,6 +69,7 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={createNewDiagram}
|
||||
disabled={databaseType === DatabaseType.GENERIC}
|
||||
>
|
||||
{t('new_diagram_dialog.empty_diagram')}
|
||||
</Button>
|
||||
|
||||
@@ -218,8 +218,14 @@ export const CreateRelationshipDialog: React.FC<
|
||||
closeCreateRelationshipDialog();
|
||||
}
|
||||
}}
|
||||
modal={false}
|
||||
>
|
||||
<DialogContent className="flex flex-col overflow-y-auto" showClose>
|
||||
<DialogContent
|
||||
className="flex flex-col overflow-y-auto"
|
||||
showClose
|
||||
forceOverlay
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{t('create_relationship_dialog.title')}
|
||||
|
||||
@@ -17,15 +17,21 @@ import { useDialog } from '@/hooks/use-dialog';
|
||||
import {
|
||||
exportBaseSQL,
|
||||
exportSQL,
|
||||
} from '@/lib/data/export-metadata/export-sql-script';
|
||||
} from '@/lib/data/sql-export/export-sql-script';
|
||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
|
||||
import { Annoyed, Sparkles } from 'lucide-react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
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 {
|
||||
targetDatabaseType: DatabaseType;
|
||||
@@ -36,7 +42,8 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
targetDatabaseType,
|
||||
}) => {
|
||||
const { closeExportSQLDialog } = useDialog();
|
||||
const { currentDiagram, filteredSchemas } = useChartDB();
|
||||
const { currentDiagram } = useChartDB();
|
||||
const { filter } = useDiagramFilter();
|
||||
const { t } = useTranslation();
|
||||
const [script, setScript] = React.useState<string>();
|
||||
const [error, setError] = React.useState<boolean>(false);
|
||||
@@ -48,7 +55,16 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
const filteredDiagram: Diagram = {
|
||||
...currentDiagram,
|
||||
tables: currentDiagram.tables?.filter((table) =>
|
||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
||||
filterTable({
|
||||
table: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||
},
|
||||
})
|
||||
),
|
||||
relationships: currentDiagram.relationships?.filter((rel) => {
|
||||
const sourceTable = currentDiagram.tables?.find(
|
||||
@@ -60,11 +76,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
return (
|
||||
sourceTable &&
|
||||
targetTable &&
|
||||
shouldShowTablesBySchemaFilter(
|
||||
sourceTable,
|
||||
filteredSchemas
|
||||
) &&
|
||||
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
|
||||
filterRelationship({
|
||||
tableA: {
|
||||
id: sourceTable.id,
|
||||
schema: sourceTable.schema,
|
||||
},
|
||||
tableB: {
|
||||
id: targetTable.id,
|
||||
schema: targetTable.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
dependencies: currentDiagram.dependencies?.filter((dep) => {
|
||||
@@ -77,11 +102,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
return (
|
||||
table &&
|
||||
dependentTable &&
|
||||
shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
|
||||
shouldShowTablesBySchemaFilter(
|
||||
dependentTable,
|
||||
filteredSchemas
|
||||
)
|
||||
filterDependency({
|
||||
tableA: {
|
||||
id: table.id,
|
||||
schema: table.schema,
|
||||
},
|
||||
tableB: {
|
||||
id: dependentTable.id,
|
||||
schema: dependentTable.schema,
|
||||
},
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
};
|
||||
@@ -101,7 +135,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
signal: abortControllerRef.current?.signal,
|
||||
});
|
||||
}
|
||||
}, [targetDatabaseType, currentDiagram, filteredSchemas]);
|
||||
}, [targetDatabaseType, currentDiagram, filter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialog.open) {
|
||||
|
||||
@@ -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 { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
||||
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {
|
||||
Suspense,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import type * as monaco from 'monaco-editor';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -36,45 +36,11 @@ 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;
|
||||
}
|
||||
import { parseDBMLError } from '@/lib/dbml/dbml-import/dbml-import-error';
|
||||
import {
|
||||
clearErrorHighlight,
|
||||
highlightErrorLine,
|
||||
} from '@/components/code-snippet/dbml/utils';
|
||||
|
||||
export interface ImportDBMLDialogProps extends BaseDialogProps {
|
||||
withCreateEmptyDiagram?: boolean;
|
||||
@@ -150,39 +116,8 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
||||
}
|
||||
}, [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();
|
||||
clearErrorHighlight(decorationsCollection.current);
|
||||
}, []);
|
||||
|
||||
const validateDBML = useCallback(
|
||||
@@ -197,7 +132,7 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
||||
const preprocessedContent = preprocessDBML(content);
|
||||
const sanitizedContent = sanitizeDBML(preprocessedContent);
|
||||
const parser = new Parser();
|
||||
parser.parse(sanitizedContent, 'dbml');
|
||||
parser.parse(sanitizedContent, 'dbmlv2');
|
||||
} catch (e) {
|
||||
const parsedError = parseDBMLError(e);
|
||||
if (parsedError) {
|
||||
@@ -205,7 +140,12 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
||||
t('import_dbml_dialog.error.description') +
|
||||
` (1 error found - in line ${parsedError.line})`
|
||||
);
|
||||
highlightErrorLine(parsedError);
|
||||
highlightErrorLine({
|
||||
error: parsedError,
|
||||
model: editorRef.current?.getModel(),
|
||||
editorDecorationsCollection:
|
||||
decorationsCollection.current,
|
||||
});
|
||||
} else {
|
||||
setErrorMessage(
|
||||
e instanceof Error ? e.message : JSON.stringify(e)
|
||||
@@ -213,7 +153,7 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
||||
}
|
||||
}
|
||||
},
|
||||
[clearDecorations, highlightErrorLine, t]
|
||||
[clearDecorations, t]
|
||||
);
|
||||
|
||||
const debouncedValidateRef = useRef<((value: string) => void) | null>(null);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/dropdown-menu/dropdown-menu';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { Ellipsis, Layers2, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import type { Diagram } from '@/lib/domain';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import { cloneDiagram } from '@/lib/clone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface DiagramRowActionsMenuProps {
|
||||
diagram: Diagram;
|
||||
onOpen: () => void;
|
||||
refetch: () => void;
|
||||
numberOfDiagrams: number;
|
||||
}
|
||||
|
||||
export const DiagramRowActionsMenu: React.FC<DiagramRowActionsMenuProps> = ({
|
||||
diagram,
|
||||
onOpen,
|
||||
refetch,
|
||||
numberOfDiagrams,
|
||||
}) => {
|
||||
const { diagramId } = useChartDB();
|
||||
const { deleteDiagram, addDiagram } = useStorage();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onDelete = useCallback(async () => {
|
||||
deleteDiagram(diagram.id);
|
||||
refetch();
|
||||
|
||||
if (diagram.id === diagramId || numberOfDiagrams <= 1) {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}, [deleteDiagram, diagram.id, diagramId, refetch, numberOfDiagrams]);
|
||||
|
||||
const onDuplicate = useCallback(async () => {
|
||||
const duplicatedDiagram = cloneDiagram(diagram);
|
||||
|
||||
const diagramToAdd = duplicatedDiagram.diagram;
|
||||
|
||||
if (!diagramToAdd) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagramToAdd.name = `${diagram.name} (Copy)`;
|
||||
|
||||
addDiagram({ diagram: diagramToAdd });
|
||||
refetch();
|
||||
}, [addDiagram, refetch, diagram]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 p-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Ellipsis className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={onOpen}
|
||||
className="flex justify-between gap-4"
|
||||
>
|
||||
{t('open_diagram_dialog.diagram_actions.open')}
|
||||
<SquareArrowOutUpRight className="size-3.5" />
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={onDuplicate}
|
||||
className="flex justify-between gap-4"
|
||||
>
|
||||
{t('open_diagram_dialog.diagram_actions.duplicate')}
|
||||
<Layers2 className="size-3.5" />
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={onDelete}
|
||||
className="flex justify-between gap-4 text-red-700"
|
||||
>
|
||||
{t('open_diagram_dialog.diagram_actions.delete')}
|
||||
<Trash2 className="size-3.5 text-red-700" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -27,6 +27,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||
import { useDebounce } from '@/hooks/use-debounce';
|
||||
import { DiagramRowActionsMenu } from './diagram-row-actions-menu/diagram-row-actions-menu';
|
||||
|
||||
export interface OpenDiagramDialogProps extends BaseDialogProps {
|
||||
canClose?: boolean;
|
||||
@@ -46,21 +47,22 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedDiagramId(undefined);
|
||||
}, [dialog.open]);
|
||||
const fetchDiagrams = useCallback(async () => {
|
||||
const diagrams = await listDiagrams({ includeTables: true });
|
||||
setDiagrams(
|
||||
diagrams.sort(
|
||||
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
|
||||
)
|
||||
);
|
||||
}, [listDiagrams]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDiagrams = async () => {
|
||||
const diagrams = await listDiagrams({ includeTables: true });
|
||||
setDiagrams(
|
||||
diagrams.sort(
|
||||
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
|
||||
)
|
||||
);
|
||||
};
|
||||
if (!dialog.open) {
|
||||
return;
|
||||
}
|
||||
setSelectedDiagramId(undefined);
|
||||
fetchDiagrams();
|
||||
}, [listDiagrams, setDiagrams, dialog.open]);
|
||||
}, [dialog.open, fetchDiagrams]);
|
||||
|
||||
const openDiagram = useCallback(
|
||||
(diagramId: string) => {
|
||||
@@ -166,6 +168,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
'open_diagram_dialog.table_columns.tables_count'
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -221,6 +224,19 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
<TableCell className="text-center">
|
||||
{diagram.tables?.length}
|
||||
</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>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,11 +17,23 @@ import type { DBSchema } from '@/lib/domain/db-schema';
|
||||
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@/components/input/input';
|
||||
import { Separator } from '@/components/separator/separator';
|
||||
import { Group, SquarePlus } from 'lucide-react';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import { Label } from '@/components/label/label';
|
||||
|
||||
export interface TableSchemaDialogProps extends BaseDialogProps {
|
||||
table?: DBTable;
|
||||
schemas: DBSchema[];
|
||||
onConfirm: ({ schema }: { schema: DBSchema }) => void;
|
||||
allowSchemaCreation?: boolean;
|
||||
}
|
||||
|
||||
export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||
@@ -29,13 +41,32 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||
table,
|
||||
schemas,
|
||||
onConfirm,
|
||||
allowSchemaCreation = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedSchemaId, setSelectedSchemaId] = React.useState<string>(
|
||||
const { databaseType } = useChartDB();
|
||||
const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
|
||||
table?.schema
|
||||
? schemaNameToSchemaId(table.schema)
|
||||
: (schemas?.[0]?.id ?? '')
|
||||
);
|
||||
const allowSchemaSelection = useMemo(
|
||||
() => schemas && schemas.length > 0,
|
||||
[schemas]
|
||||
);
|
||||
|
||||
const defaultSchemaName = useMemo(
|
||||
() => defaultSchemas?.[databaseType],
|
||||
[databaseType]
|
||||
);
|
||||
|
||||
const [isCreatingNew, setIsCreatingNew] =
|
||||
useState<boolean>(!allowSchemaSelection);
|
||||
const [newSchemaName, setNewSchemaName] = useState<string>(
|
||||
allowSchemaCreation && !allowSchemaSelection
|
||||
? (defaultSchemaName ?? '')
|
||||
: ''
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialog.open) return;
|
||||
@@ -44,15 +75,39 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||
? schemaNameToSchemaId(table.schema)
|
||||
: (schemas?.[0]?.id ?? '')
|
||||
);
|
||||
}, [dialog.open, schemas, table?.schema]);
|
||||
setIsCreatingNew(!allowSchemaSelection);
|
||||
setNewSchemaName(
|
||||
allowSchemaCreation && !allowSchemaSelection
|
||||
? (defaultSchemaName ?? '')
|
||||
: ''
|
||||
);
|
||||
}, [
|
||||
defaultSchemaName,
|
||||
dialog.open,
|
||||
schemas,
|
||||
table?.schema,
|
||||
allowSchemaSelection,
|
||||
allowSchemaCreation,
|
||||
]);
|
||||
|
||||
const { closeTableSchemaDialog } = useDialog();
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
const schema = schemas.find((s) => s.id === selectedSchemaId);
|
||||
if (!schema) return;
|
||||
if (isCreatingNew && newSchemaName.trim()) {
|
||||
const newSchema: DBSchema = {
|
||||
id: schemaNameToSchemaId(newSchemaName.trim()),
|
||||
name: newSchemaName.trim(),
|
||||
tableCount: 0,
|
||||
};
|
||||
|
||||
onConfirm({ schema });
|
||||
}, [onConfirm, selectedSchemaId, schemas]);
|
||||
onConfirm({ schema: newSchema });
|
||||
} else {
|
||||
const schema = schemas.find((s) => s.id === selectedSchemaId);
|
||||
if (!schema) return;
|
||||
|
||||
onConfirm({ schema });
|
||||
}
|
||||
}, [onConfirm, selectedSchemaId, schemas, isCreatingNew, newSchemaName]);
|
||||
|
||||
const schemaOptions: SelectBoxOption[] = useMemo(
|
||||
() =>
|
||||
@@ -63,6 +118,25 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||
[schemas]
|
||||
);
|
||||
|
||||
const renderSwitchCreateOrSelectButton = useCallback(
|
||||
() => (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-start"
|
||||
onClick={() => setIsCreatingNew(!isCreatingNew)}
|
||||
disabled={!allowSchemaSelection || !allowSchemaCreation}
|
||||
>
|
||||
{!isCreatingNew ? (
|
||||
<SquarePlus className="mr-2 size-4 " />
|
||||
) : (
|
||||
<Group className="mr-2 size-4 " />
|
||||
)}
|
||||
{isCreatingNew ? 'Select existing schema' : 'Create new schema'}
|
||||
</Button>
|
||||
),
|
||||
[isCreatingNew, allowSchemaSelection, allowSchemaCreation]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialog}
|
||||
@@ -70,48 +144,106 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||
if (!open) {
|
||||
closeTableSchemaDialog();
|
||||
}
|
||||
|
||||
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
|
||||
}}
|
||||
>
|
||||
<DialogContent className="flex flex-col" showClose>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{table
|
||||
? t('update_table_schema_dialog.title')
|
||||
: t('new_table_schema_dialog.title')}
|
||||
{!allowSchemaSelection && allowSchemaCreation
|
||||
? t('create_table_schema_dialog.title')
|
||||
: table
|
||||
? t('update_table_schema_dialog.title')
|
||||
: t('new_table_schema_dialog.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{table
|
||||
? t('update_table_schema_dialog.description', {
|
||||
tableName: table.name,
|
||||
})
|
||||
: t('new_table_schema_dialog.description')}
|
||||
{!allowSchemaSelection && allowSchemaCreation
|
||||
? t('create_table_schema_dialog.description')
|
||||
: table
|
||||
? t('update_table_schema_dialog.description', {
|
||||
tableName: table.name,
|
||||
})
|
||||
: t('new_table_schema_dialog.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-1">
|
||||
<div className="grid w-full items-center gap-4">
|
||||
<SelectBox
|
||||
options={schemaOptions}
|
||||
multiple={false}
|
||||
value={selectedSchemaId}
|
||||
onChange={(value) =>
|
||||
setSelectedSchemaId(value as string)
|
||||
}
|
||||
/>
|
||||
{!isCreatingNew ? (
|
||||
<SelectBox
|
||||
options={schemaOptions}
|
||||
multiple={false}
|
||||
value={selectedSchemaId}
|
||||
onChange={(value) =>
|
||||
setSelectedSchemaId(value as string)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{allowSchemaCreation &&
|
||||
!allowSchemaSelection ? (
|
||||
<Label htmlFor="new-schema-name">
|
||||
Schema Name
|
||||
</Label>
|
||||
) : null}
|
||||
<Input
|
||||
id="new-schema-name"
|
||||
value={newSchemaName}
|
||||
onChange={(e) =>
|
||||
setNewSchemaName(e.target.value)
|
||||
}
|
||||
placeholder={`Enter schema name.${defaultSchemaName ? ` e.g. ${defaultSchemaName}.` : ''}`}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allowSchemaCreation && allowSchemaSelection ? (
|
||||
<>
|
||||
<div className="relative">
|
||||
<Separator className="my-2" />
|
||||
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-2 text-xs text-muted-foreground">
|
||||
or
|
||||
</span>
|
||||
</div>
|
||||
{allowSchemaSelection ? (
|
||||
renderSwitchCreateOrSelectButton()
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
{renderSwitchCreateOrSelectButton()}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>No existing schemas available</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="flex gap-1 md:justify-between">
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary">
|
||||
{table
|
||||
? t('update_table_schema_dialog.cancel')
|
||||
: t('new_table_schema_dialog.cancel')}
|
||||
{isCreatingNew
|
||||
? t('create_table_schema_dialog.cancel')
|
||||
: table
|
||||
? t('update_table_schema_dialog.cancel')
|
||||
: t('new_table_schema_dialog.cancel')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button onClick={handleConfirm}>
|
||||
{table
|
||||
? t('update_table_schema_dialog.confirm')
|
||||
: t('new_table_schema_dialog.confirm')}
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={isCreatingNew && !newSchemaName.trim()}
|
||||
>
|
||||
{isCreatingNew
|
||||
? t('create_table_schema_dialog.create')
|
||||
: table
|
||||
? t('update_table_schema_dialog.confirm')
|
||||
: t('new_table_schema_dialog.confirm')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
overscroll-behavior-x: none;
|
||||
}
|
||||
|
||||
.text-editable {
|
||||
@@ -154,3 +155,29 @@
|
||||
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;
|
||||
}
|
||||
|
||||
50
src/hooks/use-click-outside.ts
Normal file
50
src/hooks/use-click-outside.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useEffect, useCallback, type RefObject } from 'react';
|
||||
|
||||
/**
|
||||
* Custom hook that handles click outside detection with capture phase
|
||||
* to work properly with React Flow canvas and other event-stopping elements
|
||||
*/
|
||||
export function useClickOutside(
|
||||
ref: RefObject<HTMLElement>,
|
||||
handler: () => void,
|
||||
isActive = true
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (!isActive) return;
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
||||
// Use capture phase to catch events before React Flow or other libraries can stop them
|
||||
document.addEventListener('mousedown', handleClickOutside, true);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside, true);
|
||||
};
|
||||
}, [ref, handler, isActive]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized version of useClickOutside for edit mode inputs
|
||||
* Adds a small delay to prevent race conditions with blur events
|
||||
*/
|
||||
export function useEditClickOutside(
|
||||
inputRef: RefObject<HTMLElement>,
|
||||
editMode: boolean,
|
||||
onSave: () => void,
|
||||
delay = 100
|
||||
) {
|
||||
const handleClickOutside = useCallback(() => {
|
||||
if (editMode) {
|
||||
// Small delay to ensure any pending state updates are processed
|
||||
setTimeout(() => {
|
||||
onSave();
|
||||
}, delay);
|
||||
}
|
||||
}, [editMode, onSave, delay]);
|
||||
|
||||
useClickOutside(inputRef, handleClickOutside, editMode);
|
||||
}
|
||||
142
src/hooks/use-focus-on.ts
Normal file
142
src/hooks/use-focus-on.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
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 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,
|
||||
focusOnRelationship,
|
||||
};
|
||||
};
|
||||
320
src/hooks/use-update-table-field.ts
Normal file
320
src/hooks/use-update-table-field.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { useChartDB } from './use-chartdb';
|
||||
import { useDebounce } from './use-debounce-v2';
|
||||
import type { DBField, DBTable } from '@/lib/domain';
|
||||
import type {
|
||||
SelectBoxOption,
|
||||
SelectBoxProps,
|
||||
} from '@/components/select-box/select-box';
|
||||
import {
|
||||
dataTypeDataToDataType,
|
||||
sortedDataTypeMap,
|
||||
} 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
|
||||
): {
|
||||
regex?: string;
|
||||
extractRegex?: RegExp;
|
||||
} => {
|
||||
if (!dataType.fieldAttributes) {
|
||||
return { regex: undefined, extractRegex: undefined };
|
||||
}
|
||||
|
||||
const typeName = dataType.name;
|
||||
const fieldAttributes = dataType.fieldAttributes;
|
||||
|
||||
if (fieldAttributes.hasCharMaxLength) {
|
||||
if (fieldAttributes.hasCharMaxLengthOption) {
|
||||
return {
|
||||
regex: `^${typeName}\\((\\d+|[mM][aA][xX])\\)$`,
|
||||
extractRegex: /\((\d+|max)\)/i,
|
||||
};
|
||||
}
|
||||
return {
|
||||
regex: `^${typeName}\\(\\d+\\)$`,
|
||||
extractRegex: /\((\d+)\)/,
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldAttributes.precision && fieldAttributes.scale) {
|
||||
return {
|
||||
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*(?:,\\s*\\d+\\s*)?\\)$`,
|
||||
extractRegex: new RegExp(
|
||||
`${typeName}\\s*\\(\\s*(\\d+)\\s*(?:,\\s*(\\d+)\\s*)?\\)`
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldAttributes.precision) {
|
||||
return {
|
||||
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*\\)$`,
|
||||
extractRegex: /\((\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);
|
||||
|
||||
// Update local state when field properties change externally
|
||||
useEffect(() => {
|
||||
setLocalFieldName(field.name);
|
||||
setLocalNullable(field.nullable);
|
||||
setLocalPrimaryKey(field.primaryKey);
|
||||
}, [field.name, 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);
|
||||
|
||||
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;
|
||||
|
||||
if (regexMatches?.length) {
|
||||
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]);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
updateField(table.id, field.id, {
|
||||
characterMaximumLength,
|
||||
precision,
|
||||
scale,
|
||||
increment: 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) => {
|
||||
updateField(table.id, field.id, { nullable: value });
|
||||
},
|
||||
[updateField, table.id, field.id]
|
||||
),
|
||||
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, {
|
||||
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,
|
||||
};
|
||||
};
|
||||
42
src/hooks/use-update-table.ts
Normal file
42
src/hooks/use-update-table.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import { useChartDB } from './use-chartdb';
|
||||
import { useDebounce } from './use-debounce-v2';
|
||||
import type { DBTable } from '@/lib/domain';
|
||||
|
||||
// Hook for updating table properties with debouncing for performance
|
||||
export const useUpdateTable = (table: DBTable) => {
|
||||
const { updateTable: chartDBUpdateTable } = useChartDB();
|
||||
const [localTableName, setLocalTableName] = useState(table.name);
|
||||
|
||||
// Debounced update function
|
||||
const debouncedUpdate = useDebounce(
|
||||
useCallback(
|
||||
(value: string) => {
|
||||
if (value.trim() && value.trim() !== table.name) {
|
||||
chartDBUpdateTable(table.id, { name: value.trim() });
|
||||
}
|
||||
},
|
||||
[chartDBUpdateTable, table.id, table.name]
|
||||
),
|
||||
1000 // 1000ms debounce
|
||||
);
|
||||
|
||||
// Update local state immediately for responsive UI
|
||||
const handleTableNameChange = useCallback(
|
||||
(value: string) => {
|
||||
setLocalTableName(value);
|
||||
debouncedUpdate(value);
|
||||
},
|
||||
[debouncedUpdate]
|
||||
);
|
||||
|
||||
// Update local state when table name changes externally
|
||||
useEffect(() => {
|
||||
setLocalTableName(table.name);
|
||||
}, [table.name]);
|
||||
|
||||
return {
|
||||
tableName: localTableName,
|
||||
handleTableNameChange,
|
||||
};
|
||||
};
|
||||
@@ -23,23 +23,25 @@ import { bn, bnMetadata } from './locales/bn';
|
||||
import { gu, guMetadata } from './locales/gu';
|
||||
import { vi, viMetadata } from './locales/vi';
|
||||
import { ar, arMetadata } from './locales/ar';
|
||||
import { hr, hrMetadata } from './locales/hr';
|
||||
|
||||
export const languages: LanguageMetadata[] = [
|
||||
enMetadata,
|
||||
esMetadata,
|
||||
frMetadata,
|
||||
deMetadata,
|
||||
esMetadata,
|
||||
ukMetadata,
|
||||
ruMetadata,
|
||||
trMetadata,
|
||||
hrMetadata,
|
||||
pt_BRMetadata,
|
||||
hiMetadata,
|
||||
jaMetadata,
|
||||
ko_KRMetadata,
|
||||
pt_BRMetadata,
|
||||
ukMetadata,
|
||||
ruMetadata,
|
||||
zh_CNMetadata,
|
||||
zh_TWMetadata,
|
||||
neMetadata,
|
||||
mrMetadata,
|
||||
trMetadata,
|
||||
id_IDMetadata,
|
||||
teMetadata,
|
||||
bnMetadata,
|
||||
@@ -70,6 +72,7 @@ const resources = {
|
||||
gu,
|
||||
vi,
|
||||
ar,
|
||||
hr,
|
||||
};
|
||||
|
||||
i18n.use(LanguageDetector)
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ar: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'جديد',
|
||||
browse: 'تصفح',
|
||||
tables: 'الجداول',
|
||||
refs: 'المراجع',
|
||||
areas: 'المناطق',
|
||||
dependencies: 'التبعيات',
|
||||
custom_types: 'الأنواع المخصصة',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ملف',
|
||||
new: 'جديد',
|
||||
open: 'فتح',
|
||||
actions: {
|
||||
actions: 'الإجراءات',
|
||||
new: 'جديد...',
|
||||
browse: 'تصفح...',
|
||||
save: 'حفظ',
|
||||
import: 'استيراد قاعدة بيانات',
|
||||
export_sql: 'SQL تصدير',
|
||||
export_as: 'تصدير كـ',
|
||||
delete_diagram: 'حذف الرسم البياني',
|
||||
exit: 'خروج',
|
||||
delete_diagram: 'حذف',
|
||||
},
|
||||
edit: {
|
||||
edit: 'تحرير',
|
||||
@@ -26,7 +34,10 @@ export const ar: LanguageTranslation = {
|
||||
hide_sidebar: 'إخفاء الشريط الجانبي',
|
||||
hide_cardinality: 'إخفاء الكاردينالية',
|
||||
show_cardinality: 'إظهار الكاردينالية',
|
||||
hide_field_attributes: 'إخفاء خصائص الحقل',
|
||||
show_field_attributes: 'إظهار خصائص الحقل',
|
||||
zoom_on_scroll: 'تكبير/تصغير عند التمرير',
|
||||
show_views: 'عروض قاعدة البيانات',
|
||||
theme: 'المظهر',
|
||||
show_dependencies: 'إظهار الاعتمادات',
|
||||
hide_dependencies: 'إخفاء الاعتمادات',
|
||||
@@ -63,22 +74,13 @@ export const ar: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'إعادة ترتيب الرسم البياني',
|
||||
title: 'ترتيب تلقائي للرسم البياني',
|
||||
description:
|
||||
'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
|
||||
reorder: 'إعادة ترتيب',
|
||||
reorder: 'ترتيب تلقائي',
|
||||
cancel: 'إلغاء',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'مخططات متعددة',
|
||||
description:
|
||||
'{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'لا شيء',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'فشل النسخ',
|
||||
@@ -113,14 +115,11 @@ export const ar: LanguageTranslation = {
|
||||
copied: '!تم النسخ',
|
||||
|
||||
side_panel: {
|
||||
schema: ':المخطط',
|
||||
filter_by_schema: 'تصفية حسب المخطط',
|
||||
search_schema: '...بحث في المخطط',
|
||||
no_schemas_found: '.لم يتم العثور على مخططات',
|
||||
view_all_options: '...عرض جميع الخيارات',
|
||||
tables_section: {
|
||||
tables: 'الجداول',
|
||||
add_table: 'إضافة جدول',
|
||||
add_view: 'إضافة عرض',
|
||||
filter: 'تصفية',
|
||||
collapse: 'طي الكل',
|
||||
// TODO: Translate
|
||||
@@ -146,11 +145,14 @@ export const ar: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'خصائص الحقل',
|
||||
unique: 'فريد',
|
||||
auto_increment: 'زيادة تلقائية',
|
||||
comments: 'تعليقات',
|
||||
no_comments: 'لا يوجد تعليقات',
|
||||
delete_field: 'حذف الحقل',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'الدقة',
|
||||
scale: 'النطاق',
|
||||
default_value: 'Default Value',
|
||||
no_default: 'No default',
|
||||
},
|
||||
@@ -158,6 +160,7 @@ export const ar: LanguageTranslation = {
|
||||
title: 'خصائص الفهرس',
|
||||
name: 'الإسم',
|
||||
unique: 'فريد',
|
||||
index_type: 'نوع الفهرس',
|
||||
delete_index: 'حذف الفهرس',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -174,12 +177,15 @@ export const ar: LanguageTranslation = {
|
||||
description: 'أنشئ جدولاً للبدء',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'العلاقات',
|
||||
refs_section: {
|
||||
refs: 'المراجع',
|
||||
filter: 'تصفية',
|
||||
add_relationship: 'إضافة علاقة',
|
||||
collapse: 'طي الكل',
|
||||
add_relationship: 'إضافة علاقة',
|
||||
relationships: 'العلاقات',
|
||||
dependencies: 'الاعتمادات',
|
||||
relationship: {
|
||||
relationship: 'العلاقة',
|
||||
primary: 'الجدول الأساسي',
|
||||
foreign: 'الجدول المرتبط',
|
||||
cardinality: 'الكاردينالية',
|
||||
@@ -189,16 +195,8 @@ export const ar: LanguageTranslation = {
|
||||
delete_relationship: 'حذف',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'لا توجد علاقات',
|
||||
description: 'إنشئ علاقة لربط الجداول',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'الاعتمادات',
|
||||
filter: 'تصفية',
|
||||
collapse: 'طي الكل',
|
||||
dependency: {
|
||||
dependency: 'الاعتماد',
|
||||
table: 'الجدول',
|
||||
dependent_table: 'عرض الاعتمادات',
|
||||
delete_dependency: 'حذف',
|
||||
@@ -208,8 +206,8 @@ export const ar: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'لا توجد اعتمادات',
|
||||
description: 'إنشاء اعتماد للبدء',
|
||||
title: 'لا توجد علاقات',
|
||||
description: 'إنشاء علاقة للبدء',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -250,12 +248,16 @@ export const ar: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'لم يتم تحديد قيم التعداد',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -269,10 +271,13 @@ export const ar: LanguageTranslation = {
|
||||
show_all: 'عرض الكل',
|
||||
undo: 'تراجع',
|
||||
redo: 'إعادة',
|
||||
reorder_diagram: 'إعادة ترتيب الرسم البياني',
|
||||
reorder_diagram: 'ترتيب تلقائي للرسم البياني',
|
||||
highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
},
|
||||
|
||||
new_diagram_dialog: {
|
||||
@@ -309,7 +314,7 @@ export const ar: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'فتح مخطط',
|
||||
title: 'فتح قاعدة بيانات',
|
||||
description: 'اختر مخططًا لفتحه من القائمة ادناه',
|
||||
table_columns: {
|
||||
name: 'الإسم',
|
||||
@@ -319,6 +324,12 @@ export const ar: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'إلغاء',
|
||||
open: 'فتح',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'فتح',
|
||||
duplicate: 'تكرار',
|
||||
delete: 'حذف',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -404,6 +415,13 @@ export const ar: LanguageTranslation = {
|
||||
cancel: 'إلغاء',
|
||||
confirm: 'تغيير',
|
||||
},
|
||||
create_table_schema_dialog: {
|
||||
title: 'إنشاء مخطط جديد',
|
||||
description:
|
||||
'لا توجد مخططات حتى الآن. قم بإنشاء أول مخطط لتنظيم جداولك.',
|
||||
create: 'إنشاء',
|
||||
cancel: 'إلغاء',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: '!ساعدنا على التحسن',
|
||||
@@ -457,6 +475,7 @@ export const ar: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'جدول جديد',
|
||||
new_view: 'عرض جديد',
|
||||
new_relationship: 'علاقة جديدة',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -478,6 +497,8 @@ export const ar: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'اللغة',
|
||||
},
|
||||
on: 'تشغيل',
|
||||
off: 'إيقاف',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const bn: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'নতুন',
|
||||
browse: 'ব্রাউজ',
|
||||
tables: 'টেবিল',
|
||||
refs: 'রেফস',
|
||||
areas: 'এলাকা',
|
||||
dependencies: 'নির্ভরতা',
|
||||
custom_types: 'কাস্টম টাইপ',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ফাইল',
|
||||
new: 'নতুন',
|
||||
open: 'খুলুন',
|
||||
actions: {
|
||||
actions: 'কার্য',
|
||||
new: 'নতুন...',
|
||||
browse: 'ব্রাউজ করুন...',
|
||||
save: 'সংরক্ষণ করুন',
|
||||
import: 'ডাটাবেস আমদানি করুন',
|
||||
export_sql: 'SQL রপ্তানি করুন',
|
||||
export_as: 'রূপে রপ্তানি করুন',
|
||||
delete_diagram: 'ডায়াগ্রাম মুছুন',
|
||||
exit: 'প্রস্থান করুন',
|
||||
delete_diagram: 'মুছুন',
|
||||
},
|
||||
edit: {
|
||||
edit: 'সম্পাদনা',
|
||||
@@ -26,7 +34,10 @@ export const bn: LanguageTranslation = {
|
||||
hide_sidebar: 'সাইডবার লুকান',
|
||||
hide_cardinality: 'কার্ডিনালিটি লুকান',
|
||||
show_cardinality: 'কার্ডিনালিটি দেখান',
|
||||
hide_field_attributes: 'ফিল্ড অ্যাট্রিবিউট লুকান',
|
||||
show_field_attributes: 'ফিল্ড অ্যাট্রিবিউট দেখান',
|
||||
zoom_on_scroll: 'স্ক্রলে জুম করুন',
|
||||
show_views: 'ডাটাবেস ভিউ',
|
||||
theme: 'থিম',
|
||||
show_dependencies: 'নির্ভরতাগুলি দেখান',
|
||||
hide_dependencies: 'নির্ভরতাগুলি লুকান',
|
||||
@@ -64,22 +75,13 @@ export const bn: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
|
||||
title: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
|
||||
description:
|
||||
'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?',
|
||||
reorder: 'পুনর্বিন্যাস করুন',
|
||||
reorder: 'স্বয়ংক্রিয় সাজান',
|
||||
cancel: 'বাতিল করুন',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'বহু স্কিমা',
|
||||
description:
|
||||
'{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'কিছুই না',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'কপি ব্যর্থ হয়েছে',
|
||||
@@ -114,14 +116,11 @@ export const bn: LanguageTranslation = {
|
||||
copied: 'অনুলিপি সম্পন্ন!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'স্কিমা:',
|
||||
filter_by_schema: 'স্কিমা দ্বারা ফিল্টার করুন',
|
||||
search_schema: 'স্কিমা খুঁজুন...',
|
||||
no_schemas_found: 'কোনো স্কিমা পাওয়া যায়নি।',
|
||||
view_all_options: 'সমস্ত বিকল্প দেখুন...',
|
||||
tables_section: {
|
||||
tables: 'টেবিল',
|
||||
add_table: 'টেবিল যোগ করুন',
|
||||
add_view: 'ভিউ যোগ করুন',
|
||||
filter: 'ফিল্টার',
|
||||
collapse: 'সব ভাঁজ করুন',
|
||||
// TODO: Translate
|
||||
@@ -147,6 +146,7 @@ export const bn: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'ফিল্ড কর্ম',
|
||||
unique: 'অদ্বিতীয়',
|
||||
auto_increment: 'স্বয়ংক্রিয় বৃদ্ধি',
|
||||
comments: 'মন্তব্য',
|
||||
no_comments: 'কোনো মন্তব্য নেই',
|
||||
delete_field: 'ফিল্ড মুছুন',
|
||||
@@ -155,11 +155,14 @@ export const bn: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'নির্ভুলতা',
|
||||
scale: 'স্কেল',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'ইনডেক্স কর্ম',
|
||||
name: 'নাম',
|
||||
unique: 'অদ্বিতীয়',
|
||||
index_type: 'ইনডেক্স ধরন',
|
||||
delete_index: 'ইনডেক্স মুছুন',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -176,14 +179,17 @@ export const bn: LanguageTranslation = {
|
||||
description: 'শুরু করতে একটি টেবিল তৈরি করুন',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'সম্পর্ক',
|
||||
refs_section: {
|
||||
refs: 'রেফস',
|
||||
filter: 'ফিল্টার',
|
||||
add_relationship: 'সম্পর্ক যোগ করুন',
|
||||
collapse: 'সব ভাঁজ করুন',
|
||||
add_relationship: 'সম্পর্ক যোগ করুন',
|
||||
relationships: 'সম্পর্ক',
|
||||
dependencies: 'নির্ভরতাগুলি',
|
||||
relationship: {
|
||||
relationship: 'সম্পর্ক',
|
||||
primary: 'প্রাথমিক টেবিল',
|
||||
foreign: 'বিদেশি টেবিল',
|
||||
foreign: 'রেফারেন্স করা টেবিল',
|
||||
cardinality: 'কার্ডিনালিটি',
|
||||
delete_relationship: 'মুছুন',
|
||||
relationship_actions: {
|
||||
@@ -191,27 +197,19 @@ export const bn: LanguageTranslation = {
|
||||
delete_relationship: 'মুছুন',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'কোনো সম্পর্ক নেই',
|
||||
description: 'টেবিল সংযোগ করতে একটি সম্পর্ক তৈরি করুন',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'নির্ভরতাগুলি',
|
||||
filter: 'ফিল্টার',
|
||||
collapse: 'ভাঁজ করুন',
|
||||
dependency: {
|
||||
dependency: 'নির্ভরতা',
|
||||
table: 'টেবিল',
|
||||
dependent_table: 'নির্ভরশীল টেবিল',
|
||||
delete_dependency: 'নির্ভরতা মুছুন',
|
||||
dependent_table: 'নির্ভরশীল ভিউ',
|
||||
delete_dependency: 'মুছুন',
|
||||
dependency_actions: {
|
||||
title: 'কর্ম',
|
||||
delete_dependency: 'নির্ভরতা মুছুন',
|
||||
delete_dependency: 'মুছুন',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'কোনো নির্ভরতাগুলি নেই',
|
||||
description: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।',
|
||||
title: 'কোনো সম্পর্ক নেই',
|
||||
description: 'শুরু করতে একটি সম্পর্ক তৈরি করুন',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -251,12 +249,16 @@ export const bn: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'কোন enum মান সংজ্ঞায়িত নেই',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -270,9 +272,13 @@ export const bn: LanguageTranslation = {
|
||||
show_all: 'সব দেখান',
|
||||
undo: 'পূর্বাবস্থায় ফিরুন',
|
||||
redo: 'পুনরায় করুন',
|
||||
reorder_diagram: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
|
||||
reorder_diagram: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
|
||||
highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন',
|
||||
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
filter: 'Filter Tables',
|
||||
},
|
||||
|
||||
@@ -310,7 +316,7 @@ export const bn: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'চিত্র খুলুন',
|
||||
title: 'ডেটাবেস খুলুন',
|
||||
description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।',
|
||||
table_columns: {
|
||||
name: 'নাম',
|
||||
@@ -320,6 +326,12 @@ export const bn: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'বাতিল করুন',
|
||||
open: 'খুলুন',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'খুলুন',
|
||||
duplicate: 'ডুপ্লিকেট',
|
||||
delete: 'মুছুন',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -405,6 +417,13 @@ export const bn: LanguageTranslation = {
|
||||
cancel: 'বাতিল করুন',
|
||||
confirm: 'পরিবর্তন করুন',
|
||||
},
|
||||
create_table_schema_dialog: {
|
||||
title: 'নতুন স্কিমা তৈরি করুন',
|
||||
description:
|
||||
'এখনও কোনো স্কিমা নেই। আপনার টেবিলগুলি সংগঠিত করতে আপনার প্রথম স্কিমা তৈরি করুন।',
|
||||
create: 'তৈরি করুন',
|
||||
cancel: 'বাতিল করুন',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'আমাদের উন্নত করতে সাহায্য করুন!',
|
||||
@@ -461,6 +480,7 @@ export const bn: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'নতুন টেবিল',
|
||||
new_view: 'নতুন ভিউ',
|
||||
new_relationship: 'নতুন সম্পর্ক',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -482,6 +502,9 @@ export const bn: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'ভাষা পরিবর্তন করুন',
|
||||
},
|
||||
|
||||
on: 'চালু',
|
||||
off: 'বন্ধ',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const de: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Neu',
|
||||
browse: 'Durchsuchen',
|
||||
tables: 'Tabellen',
|
||||
refs: 'Refs',
|
||||
areas: 'Bereiche',
|
||||
dependencies: 'Abhängigkeiten',
|
||||
custom_types: 'Benutzerdefinierte Typen',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Datei',
|
||||
new: 'Neu',
|
||||
open: 'Öffnen',
|
||||
actions: {
|
||||
actions: 'Aktionen',
|
||||
new: 'Neu...',
|
||||
browse: 'Durchsuchen...',
|
||||
save: 'Speichern',
|
||||
import: 'Datenbank importieren',
|
||||
export_sql: 'SQL exportieren',
|
||||
export_as: 'Exportieren als',
|
||||
delete_diagram: 'Diagramm löschen',
|
||||
exit: 'Beenden',
|
||||
delete_diagram: 'Löschen',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Bearbeiten',
|
||||
@@ -26,7 +34,10 @@ export const de: LanguageTranslation = {
|
||||
hide_sidebar: 'Seitenleiste ausblenden',
|
||||
hide_cardinality: 'Kardinalität ausblenden',
|
||||
show_cardinality: 'Kardinalität anzeigen',
|
||||
hide_field_attributes: 'Feldattribute ausblenden',
|
||||
show_field_attributes: 'Feldattribute anzeigen',
|
||||
zoom_on_scroll: 'Zoom beim Scrollen',
|
||||
show_views: 'Datenbankansichten',
|
||||
theme: 'Stil',
|
||||
show_dependencies: 'Abhängigkeiten anzeigen',
|
||||
hide_dependencies: 'Abhängigkeiten ausblenden',
|
||||
@@ -64,22 +75,13 @@ export const de: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Diagramm neu anordnen',
|
||||
title: 'Diagramm automatisch anordnen',
|
||||
description:
|
||||
'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?',
|
||||
reorder: 'Neu anordnen',
|
||||
reorder: 'Automatisch anordnen',
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Kopieren fehlgeschlagen',
|
||||
@@ -115,14 +117,11 @@ export const de: LanguageTranslation = {
|
||||
copied: 'Kopiert!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tabellen',
|
||||
add_table: 'Tabelle hinzufügen',
|
||||
add_view: 'Ansicht hinzufügen',
|
||||
filter: 'Filter',
|
||||
collapse: 'Alle einklappen',
|
||||
// TODO: Translate
|
||||
@@ -148,6 +147,7 @@ export const de: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Feldattribute',
|
||||
unique: 'Eindeutig',
|
||||
auto_increment: 'Automatisch hochzählen',
|
||||
comments: 'Kommentare',
|
||||
no_comments: 'Keine Kommentare',
|
||||
delete_field: 'Feld löschen',
|
||||
@@ -156,11 +156,14 @@ export const de: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Präzision',
|
||||
scale: 'Skalierung',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Indexattribute',
|
||||
name: 'Name',
|
||||
unique: 'Eindeutig',
|
||||
index_type: 'Indextyp',
|
||||
delete_index: 'Index löschen',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -177,32 +180,26 @@ export const de: LanguageTranslation = {
|
||||
description: 'Erstellen Sie eine Tabelle, um zu beginnen',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Beziehungen',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Filter',
|
||||
add_relationship: 'Beziehung hinzufügen',
|
||||
collapse: 'Alle einklappen',
|
||||
add_relationship: 'Beziehung hinzufügen',
|
||||
relationships: 'Beziehungen',
|
||||
dependencies: 'Abhängigkeiten',
|
||||
relationship: {
|
||||
relationship: 'Beziehung',
|
||||
primary: 'Primäre Tabelle',
|
||||
foreign: 'Referenzierte Tabelle',
|
||||
cardinality: 'Kardinalität',
|
||||
delete_relationship: 'Beziehung löschen',
|
||||
delete_relationship: 'Löschen',
|
||||
relationship_actions: {
|
||||
title: 'Aktionen',
|
||||
delete_relationship: 'Beziehung löschen',
|
||||
delete_relationship: 'Löschen',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Keine Beziehungen',
|
||||
description:
|
||||
'Erstellen Sie eine Beziehung, um Tabellen zu verbinden',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'Abhängigkeiten',
|
||||
filter: 'Filter',
|
||||
collapse: 'Alle einklappen',
|
||||
dependency: {
|
||||
dependency: 'Abhängigkeit',
|
||||
table: 'Tabelle',
|
||||
dependent_table: 'Abhängige Ansicht',
|
||||
delete_dependency: 'Löschen',
|
||||
@@ -212,8 +209,8 @@ export const de: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Keine Abhängigkeiten',
|
||||
description: 'Erstellen Sie eine Ansicht, um zu beginnen',
|
||||
title: 'Keine Beziehungen',
|
||||
description: 'Erstellen Sie eine Beziehung, um zu beginnen',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -253,12 +250,16 @@ export const de: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'Keine Enum-Werte definiert',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -272,7 +273,12 @@ export const de: LanguageTranslation = {
|
||||
show_all: 'Alle anzeigen',
|
||||
undo: 'Rückgängig',
|
||||
redo: 'Wiederholen',
|
||||
reorder_diagram: 'Diagramm neu anordnen',
|
||||
reorder_diagram: 'Diagramm automatisch anordnen',
|
||||
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Überlappende Tabellen hervorheben',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -313,7 +319,7 @@ export const de: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Diagramm öffnen',
|
||||
title: 'Datenbank öffnen',
|
||||
description: 'Wählen Sie ein Diagramm aus der Liste unten aus.',
|
||||
table_columns: {
|
||||
name: 'Name',
|
||||
@@ -323,6 +329,12 @@ export const de: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Abbrechen',
|
||||
open: 'Öffnen',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Öffnen',
|
||||
duplicate: 'Duplizieren',
|
||||
delete: 'Löschen',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -408,6 +420,13 @@ export const de: LanguageTranslation = {
|
||||
cancel: 'Abbrechen',
|
||||
confirm: 'Ändern',
|
||||
},
|
||||
create_table_schema_dialog: {
|
||||
title: 'Neues Schema erstellen',
|
||||
description:
|
||||
'Es existieren noch keine Schemas. Erstellen Sie Ihr erstes Schema, um Ihre Tabellen zu organisieren.',
|
||||
create: 'Erstellen',
|
||||
cancel: 'Abbrechen',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Hilf uns, uns zu verbessern!',
|
||||
@@ -464,6 +483,7 @@ export const de: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Neue Tabelle',
|
||||
new_view: 'Neue Ansicht',
|
||||
new_relationship: 'Neue Beziehung',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -486,6 +506,9 @@ export const de: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Sprache',
|
||||
},
|
||||
|
||||
on: 'Ein',
|
||||
off: 'Aus',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata } from '../types';
|
||||
|
||||
export const en = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'New',
|
||||
browse: 'Browse',
|
||||
tables: 'Tables',
|
||||
refs: 'Refs',
|
||||
areas: 'Areas',
|
||||
dependencies: 'Dependencies',
|
||||
custom_types: 'Custom Types',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'File',
|
||||
new: 'New',
|
||||
open: 'Open',
|
||||
actions: {
|
||||
actions: 'Actions',
|
||||
new: 'New...',
|
||||
browse: 'Browse...',
|
||||
save: 'Save',
|
||||
import: 'Import',
|
||||
export_sql: 'Export SQL',
|
||||
export_as: 'Export as',
|
||||
delete_diagram: 'Delete Diagram',
|
||||
exit: 'Exit',
|
||||
delete_diagram: 'Delete',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Edit',
|
||||
@@ -26,7 +34,10 @@ export const en = {
|
||||
hide_sidebar: 'Hide Sidebar',
|
||||
hide_cardinality: 'Hide Cardinality',
|
||||
show_cardinality: 'Show Cardinality',
|
||||
hide_field_attributes: 'Hide Field Attributes',
|
||||
show_field_attributes: 'Show Field Attributes',
|
||||
zoom_on_scroll: 'Zoom on Scroll',
|
||||
show_views: 'Database Views',
|
||||
theme: 'Theme',
|
||||
show_dependencies: 'Show Dependencies',
|
||||
hide_dependencies: 'Hide Dependencies',
|
||||
@@ -62,21 +73,13 @@ export const en = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Reorder Diagram',
|
||||
title: 'Auto Arrange Diagram',
|
||||
description:
|
||||
'This action will rearrange all tables in the diagram. Do you want to continue?',
|
||||
reorder: 'Reorder',
|
||||
reorder: 'Auto Arrange',
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Copy failed',
|
||||
@@ -111,14 +114,11 @@ export const en = {
|
||||
copied: 'Copied!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tables',
|
||||
add_table: 'Add Table',
|
||||
add_view: 'Add View',
|
||||
filter: 'Filter',
|
||||
collapse: 'Collapse All',
|
||||
clear: 'Clear Filter',
|
||||
@@ -142,7 +142,10 @@ export const en = {
|
||||
field_actions: {
|
||||
title: 'Field Attributes',
|
||||
unique: 'Unique',
|
||||
auto_increment: 'Auto Increment',
|
||||
character_length: 'Max Length',
|
||||
precision: 'Precision',
|
||||
scale: 'Scale',
|
||||
comments: 'Comments',
|
||||
no_comments: 'No comments',
|
||||
default_value: 'Default Value',
|
||||
@@ -153,6 +156,7 @@ export const en = {
|
||||
title: 'Index Attributes',
|
||||
name: 'Name',
|
||||
unique: 'Unique',
|
||||
index_type: 'Index Type',
|
||||
delete_index: 'Delete Index',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -169,12 +173,15 @@ export const en = {
|
||||
description: 'Create a table to get started',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Relationships',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Filter',
|
||||
add_relationship: 'Add Relationship',
|
||||
collapse: 'Collapse All',
|
||||
add_relationship: 'Add Relationship',
|
||||
relationships: 'Relationships',
|
||||
dependencies: 'Dependencies',
|
||||
relationship: {
|
||||
relationship: 'Relationship',
|
||||
primary: 'Primary Table',
|
||||
foreign: 'Referenced Table',
|
||||
cardinality: 'Cardinality',
|
||||
@@ -184,16 +191,8 @@ export const en = {
|
||||
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',
|
||||
table: 'Table',
|
||||
dependent_table: 'Dependent View',
|
||||
delete_dependency: 'Delete',
|
||||
@@ -203,8 +202,8 @@ export const en = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'No dependencies',
|
||||
description: 'Create a view to get started',
|
||||
title: 'No relationships',
|
||||
description: 'Create a relationship to get started',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -243,11 +242,15 @@ export const en = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'No enum values defined',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
delete_custom_type: 'Delete',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
@@ -262,8 +265,11 @@ export const en = {
|
||||
show_all: 'Show All',
|
||||
undo: 'Undo',
|
||||
redo: 'Redo',
|
||||
reorder_diagram: 'Reorder Diagram',
|
||||
reorder_diagram: 'Auto Arrange Diagram',
|
||||
highlight_overlapping_tables: 'Highlight Overlapping Tables',
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
filter: 'Filter Tables',
|
||||
},
|
||||
|
||||
@@ -301,7 +307,7 @@ export const en = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Open Diagram',
|
||||
title: 'Open Database',
|
||||
description: 'Select a diagram to open from the list below.',
|
||||
table_columns: {
|
||||
name: 'Name',
|
||||
@@ -311,6 +317,12 @@ export const en = {
|
||||
},
|
||||
cancel: 'Cancel',
|
||||
open: 'Open',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Open',
|
||||
duplicate: 'Duplicate',
|
||||
delete: 'Delete',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -396,6 +408,14 @@ export const en = {
|
||||
confirm: 'Change',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Create New Schema',
|
||||
description:
|
||||
'No schemas exist yet. Create your first schema to organize your tables.',
|
||||
create: 'Create',
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Help us improve!',
|
||||
description:
|
||||
@@ -450,6 +470,7 @@ export const en = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'New Table',
|
||||
new_view: 'New View',
|
||||
new_relationship: 'New Relationship',
|
||||
new_area: 'New Area',
|
||||
},
|
||||
@@ -470,6 +491,9 @@ export const en = {
|
||||
language_select: {
|
||||
change_language: 'Language',
|
||||
},
|
||||
|
||||
on: 'On',
|
||||
off: 'Off',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const es: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Nuevo',
|
||||
browse: 'Examinar',
|
||||
tables: 'Tablas',
|
||||
refs: 'Refs',
|
||||
areas: 'Áreas',
|
||||
dependencies: 'Dependencias',
|
||||
custom_types: 'Tipos Personalizados',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Archivo',
|
||||
new: 'Nuevo',
|
||||
open: 'Abrir',
|
||||
actions: {
|
||||
actions: 'Acciones',
|
||||
new: 'Nuevo...',
|
||||
browse: 'Examinar...',
|
||||
save: 'Guardar',
|
||||
import: 'Importar Base de Datos',
|
||||
export_sql: 'Exportar SQL',
|
||||
export_as: 'Exportar como',
|
||||
delete_diagram: 'Eliminar Diagrama',
|
||||
exit: 'Salir',
|
||||
delete_diagram: 'Eliminar',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Editar',
|
||||
@@ -24,9 +32,12 @@ export const es: LanguageTranslation = {
|
||||
view: 'Ver',
|
||||
hide_cardinality: 'Ocultar Cardinalidad',
|
||||
show_cardinality: 'Mostrar Cardinalidad',
|
||||
show_field_attributes: 'Mostrar Atributos de Campo',
|
||||
hide_field_attributes: 'Ocultar Atributos de Campo',
|
||||
show_sidebar: 'Mostrar Barra Lateral',
|
||||
hide_sidebar: 'Ocultar Barra Lateral',
|
||||
zoom_on_scroll: 'Zoom al Desplazarse',
|
||||
show_views: 'Vistas de Base de Datos',
|
||||
theme: 'Tema',
|
||||
show_dependencies: 'Mostrar dependencias',
|
||||
hide_dependencies: 'Ocultar dependencias',
|
||||
@@ -63,10 +74,10 @@ export const es: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Reordenar Diagrama',
|
||||
title: 'Organizar Diagrama Automáticamente',
|
||||
description:
|
||||
'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?',
|
||||
reorder: 'Reordenar',
|
||||
reorder: 'Organizar Automáticamente',
|
||||
cancel: 'Cancelar',
|
||||
},
|
||||
|
||||
@@ -104,14 +115,11 @@ export const es: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tablas',
|
||||
add_table: 'Agregar Tabla',
|
||||
add_view: 'Agregar Vista',
|
||||
filter: 'Filtrar',
|
||||
collapse: 'Colapsar Todo',
|
||||
// TODO: Translate
|
||||
@@ -137,6 +145,7 @@ export const es: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Atributos del Campo',
|
||||
unique: 'Único',
|
||||
auto_increment: 'Autoincremento',
|
||||
comments: 'Comentarios',
|
||||
no_comments: 'Sin comentarios',
|
||||
delete_field: 'Eliminar Campo',
|
||||
@@ -145,11 +154,14 @@ export const es: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Precisión',
|
||||
scale: 'Escala',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Atributos del Índice',
|
||||
name: 'Nombre',
|
||||
unique: 'Único',
|
||||
index_type: 'Tipo de Índice',
|
||||
delete_index: 'Eliminar Índice',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -166,14 +178,17 @@ export const es: LanguageTranslation = {
|
||||
description: 'Crea una tabla para comenzar',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Relaciones',
|
||||
add_relationship: 'Agregar Relación',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Filtrar',
|
||||
collapse: 'Colapsar Todo',
|
||||
add_relationship: 'Agregar Relación',
|
||||
relationships: 'Relaciones',
|
||||
dependencies: 'Dependencias',
|
||||
relationship: {
|
||||
primary: 'Primaria',
|
||||
foreign: 'Foránea',
|
||||
relationship: 'Relación',
|
||||
primary: 'Tabla Primaria',
|
||||
foreign: 'Tabla Referenciada',
|
||||
cardinality: 'Cardinalidad',
|
||||
delete_relationship: 'Eliminar',
|
||||
relationship_actions: {
|
||||
@@ -181,18 +196,10 @@ export const es: LanguageTranslation = {
|
||||
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: 'Dependencia',
|
||||
table: 'Tabla',
|
||||
dependent_table: 'Vista dependiente',
|
||||
dependent_table: 'Vista Dependiente',
|
||||
delete_dependency: 'Eliminar',
|
||||
dependency_actions: {
|
||||
title: 'Acciones',
|
||||
@@ -200,8 +207,8 @@ export const es: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Sin dependencias',
|
||||
description: 'Crea una vista para comenzar',
|
||||
title: 'Sin relaciones',
|
||||
description: 'Crea una relación para comenzar',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -241,12 +248,16 @@ export const es: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'No hay valores de enum definidos',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -260,7 +271,11 @@ export const es: LanguageTranslation = {
|
||||
show_all: 'Mostrar Todo',
|
||||
undo: 'Deshacer',
|
||||
redo: 'Rehacer',
|
||||
reorder_diagram: 'Reordenar Diagrama',
|
||||
reorder_diagram: 'Organizar Diagrama Automáticamente',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Resaltar tablas superpuestas',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -301,7 +316,7 @@ export const es: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Abrir Diagrama',
|
||||
title: 'Abrir Base de Datos',
|
||||
description:
|
||||
'Selecciona un diagrama para abrir de la lista a continuación.',
|
||||
table_columns: {
|
||||
@@ -312,6 +327,12 @@ export const es: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Cancelar',
|
||||
open: 'Abrir',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Abrir',
|
||||
duplicate: 'Duplicar',
|
||||
delete: 'Eliminar',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -397,6 +418,13 @@ export const es: LanguageTranslation = {
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Cambiar',
|
||||
},
|
||||
create_table_schema_dialog: {
|
||||
title: 'Crear Nuevo Esquema',
|
||||
description:
|
||||
'Aún no existen esquemas. Crea tu primer esquema para organizar tus tablas.',
|
||||
create: 'Crear',
|
||||
cancel: 'Cancelar',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: '¡Ayúdanos a mejorar!',
|
||||
@@ -406,14 +434,6 @@ export const es: LanguageTranslation = {
|
||||
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
|
||||
export_diagram_dialog: {
|
||||
title: 'Export Diagram',
|
||||
@@ -462,6 +482,7 @@ export const es: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Nueva Tabla',
|
||||
new_view: 'Nueva Vista',
|
||||
new_relationship: 'Nueva Relación',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -484,6 +505,9 @@ export const es: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Idioma',
|
||||
},
|
||||
|
||||
on: 'Encendido',
|
||||
off: 'Apagado',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const fr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Nouveau',
|
||||
browse: 'Parcourir',
|
||||
tables: 'Tables',
|
||||
refs: 'Refs',
|
||||
areas: 'Zones',
|
||||
dependencies: 'Dépendances',
|
||||
custom_types: 'Types Personnalisés',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Fichier',
|
||||
new: 'Nouveau',
|
||||
open: 'Ouvrir',
|
||||
actions: {
|
||||
actions: 'Actions',
|
||||
new: 'Nouveau...',
|
||||
browse: 'Parcourir...',
|
||||
save: 'Enregistrer',
|
||||
import: 'Importer Base de Données',
|
||||
export_sql: 'Exporter SQL',
|
||||
export_as: 'Exporter en tant que',
|
||||
delete_diagram: 'Supprimer le Diagramme',
|
||||
exit: 'Quitter',
|
||||
delete_diagram: 'Supprimer',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Édition',
|
||||
@@ -26,7 +34,10 @@ export const fr: LanguageTranslation = {
|
||||
hide_sidebar: 'Cacher la Barre Latérale',
|
||||
hide_cardinality: 'Cacher la Cardinalité',
|
||||
show_cardinality: 'Afficher la Cardinalité',
|
||||
hide_field_attributes: 'Masquer les Attributs de Champ',
|
||||
show_field_attributes: 'Afficher les Attributs de Champ',
|
||||
zoom_on_scroll: 'Zoom sur le Défilement',
|
||||
show_views: 'Vues de Base de Données',
|
||||
theme: 'Thème',
|
||||
show_dependencies: 'Afficher les Dépendances',
|
||||
hide_dependencies: 'Masquer les Dépendances',
|
||||
@@ -62,10 +73,10 @@ export const fr: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Réorganiser le Diagramme',
|
||||
title: 'Organiser Automatiquement le Diagramme',
|
||||
description:
|
||||
'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?',
|
||||
reorder: 'Réorganiser',
|
||||
reorder: 'Organiser Automatiquement',
|
||||
cancel: 'Annuler',
|
||||
},
|
||||
|
||||
@@ -103,14 +114,11 @@ export const fr: LanguageTranslation = {
|
||||
copied: 'Copié !',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tables',
|
||||
add_table: 'Ajouter une Table',
|
||||
add_view: 'Ajouter une Vue',
|
||||
filter: 'Filtrer',
|
||||
collapse: 'Réduire Tout',
|
||||
clear: 'Effacer le Filtre',
|
||||
@@ -135,6 +143,7 @@ export const fr: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Attributs du Champ',
|
||||
unique: 'Unique',
|
||||
auto_increment: 'Auto-incrément',
|
||||
comments: 'Commentaires',
|
||||
no_comments: 'Pas de commentaires',
|
||||
delete_field: 'Supprimer le Champ',
|
||||
@@ -143,11 +152,14 @@ export const fr: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Précision',
|
||||
scale: 'Échelle',
|
||||
},
|
||||
index_actions: {
|
||||
title: "Attributs de l'Index",
|
||||
name: 'Nom',
|
||||
unique: 'Unique',
|
||||
index_type: "Type d'index",
|
||||
delete_index: "Supprimer l'Index",
|
||||
},
|
||||
table_actions: {
|
||||
@@ -164,12 +176,15 @@ export const fr: LanguageTranslation = {
|
||||
description: 'Créez une table pour commencer',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Relations',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Filtrer',
|
||||
add_relationship: 'Ajouter une Relation',
|
||||
collapse: 'Réduire Tout',
|
||||
add_relationship: 'Ajouter une Relation',
|
||||
relationships: 'Relations',
|
||||
dependencies: 'Dépendances',
|
||||
relationship: {
|
||||
relationship: 'Relation',
|
||||
primary: 'Table Principale',
|
||||
foreign: 'Table Référencée',
|
||||
cardinality: 'Cardinalité',
|
||||
@@ -179,16 +194,8 @@ export const fr: LanguageTranslation = {
|
||||
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: 'Dépendance',
|
||||
table: 'Table',
|
||||
dependent_table: 'Vue Dépendante',
|
||||
delete_dependency: 'Supprimer',
|
||||
@@ -198,8 +205,8 @@ export const fr: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Aucune dépendance',
|
||||
description: 'Créez une vue pour commencer',
|
||||
title: 'Aucune relation',
|
||||
description: 'Créez une relation pour commencer',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -239,12 +246,16 @@ export const fr: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: "Aucune valeur d'énumération définie",
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -258,7 +269,11 @@ export const fr: LanguageTranslation = {
|
||||
show_all: 'Afficher Tout',
|
||||
undo: 'Annuler',
|
||||
redo: 'Rétablir',
|
||||
reorder_diagram: 'Réorganiser le Diagramme',
|
||||
reorder_diagram: 'Organiser Automatiquement le Diagramme',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Surligner les tables chevauchées',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -298,7 +313,7 @@ export const fr: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Ouvrir Diagramme',
|
||||
title: 'Ouvrir Base de Données',
|
||||
description:
|
||||
'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.',
|
||||
table_columns: {
|
||||
@@ -309,6 +324,12 @@ export const fr: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Annuler',
|
||||
open: 'Ouvrir',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Ouvrir',
|
||||
duplicate: 'Dupliquer',
|
||||
delete: 'Supprimer',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -346,15 +367,6 @@ export const fr: LanguageTranslation = {
|
||||
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: {
|
||||
title: 'Sélectionner un Schéma',
|
||||
description:
|
||||
@@ -377,6 +389,13 @@ export const fr: LanguageTranslation = {
|
||||
cancel: 'Annuler',
|
||||
confirm: 'Modifier',
|
||||
},
|
||||
create_table_schema_dialog: {
|
||||
title: 'Créer un Nouveau Schéma',
|
||||
description:
|
||||
"Aucun schéma n'existe encore. Créez votre premier schéma pour organiser vos tables.",
|
||||
create: 'Créer',
|
||||
cancel: 'Annuler',
|
||||
},
|
||||
|
||||
create_relationship_dialog: {
|
||||
title: 'Créer une Relation',
|
||||
@@ -459,6 +478,7 @@ export const fr: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Nouvelle Table',
|
||||
new_view: 'Nouvelle Vue',
|
||||
new_relationship: 'Nouvelle Relation',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -481,6 +501,9 @@ export const fr: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Langue',
|
||||
},
|
||||
|
||||
on: 'Activé',
|
||||
off: 'Désactivé',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const gu: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'નવું',
|
||||
browse: 'બ્રાઉજ',
|
||||
tables: 'ટેબલો',
|
||||
refs: 'રેફ્સ',
|
||||
areas: 'ક્ષેત્રો',
|
||||
dependencies: 'નિર્ભરતાઓ',
|
||||
custom_types: 'કસ્ટમ ટાઇપ',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ફાઇલ',
|
||||
new: 'નવું',
|
||||
open: 'ખોલો',
|
||||
actions: {
|
||||
actions: 'ક્રિયાઓ',
|
||||
new: 'નવું...',
|
||||
browse: 'બ્રાઉજ કરો...',
|
||||
save: 'સાચવો',
|
||||
import: 'ડેટાબેસ આયાત કરો',
|
||||
export_sql: 'SQL નિકાસ કરો',
|
||||
export_as: 'રૂપે નિકાસ કરો',
|
||||
delete_diagram: 'ડાયાગ્રામ કાઢી નાખો',
|
||||
exit: 'બહાર જાઓ',
|
||||
delete_diagram: 'કાઢી નાખો',
|
||||
},
|
||||
edit: {
|
||||
edit: 'ફેરફાર',
|
||||
@@ -26,7 +34,10 @@ export const gu: LanguageTranslation = {
|
||||
hide_sidebar: 'સાઇડબાર છુપાવો',
|
||||
hide_cardinality: 'કાર્ડિનાલિટી છુપાવો',
|
||||
show_cardinality: 'કાર્ડિનાલિટી બતાવો',
|
||||
hide_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ છુપાવો',
|
||||
show_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ બતાવો',
|
||||
zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો',
|
||||
show_views: 'ડેટાબેઝ વ્યૂઝ',
|
||||
theme: 'થિમ',
|
||||
show_dependencies: 'નિર્ભરતાઓ બતાવો',
|
||||
hide_dependencies: 'નિર્ભરતાઓ છુપાવો',
|
||||
@@ -64,22 +75,13 @@ export const gu: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'ડાયાગ્રામ ફરી વ્યવસ્થિત કરો',
|
||||
title: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
|
||||
description:
|
||||
'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?',
|
||||
reorder: 'ફરી વ્યવસ્થિત કરો',
|
||||
reorder: 'ઑટોમેટિક ગોઠવો',
|
||||
cancel: 'રદ કરો',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'કઈંક વધારે સ્કીમા',
|
||||
description:
|
||||
'{{schemasCount}} સ્કીમા આ ડાયાગ્રામમાં છે. હાલમાં દર્શાવેલ છે: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'કઈ નહીં',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'નકલ નિષ્ફળ',
|
||||
@@ -114,14 +116,11 @@ export const gu: LanguageTranslation = {
|
||||
copied: 'નકલ થયું!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'સ્કીમા:',
|
||||
filter_by_schema: 'સ્કીમા દ્વારા ફિલ્ટર કરો',
|
||||
search_schema: 'સ્કીમા શોધો...',
|
||||
no_schemas_found: 'કોઈ સ્કીમા મળ્યા નથી.',
|
||||
view_all_options: 'બધા વિકલ્પો જુઓ...',
|
||||
tables_section: {
|
||||
tables: 'ટેબલ્સ',
|
||||
add_table: 'ટેબલ ઉમેરો',
|
||||
add_view: 'વ્યૂ ઉમેરો',
|
||||
filter: 'ફિલ્ટર',
|
||||
collapse: 'બધાને સકુચિત કરો',
|
||||
// TODO: Translate
|
||||
@@ -148,6 +147,7 @@ export const gu: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'ફીલ્ડ લક્ષણો',
|
||||
unique: 'અદ્વિતીય',
|
||||
auto_increment: 'ઑટો ઇન્ક્રિમેન્ટ',
|
||||
comments: 'ટિપ્પણીઓ',
|
||||
no_comments: 'કોઈ ટિપ્પણીઓ નથી',
|
||||
delete_field: 'ફીલ્ડ કાઢી નાખો',
|
||||
@@ -156,11 +156,14 @@ export const gu: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'ચોકસાઈ',
|
||||
scale: 'માપ',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'ઇન્ડેક્સ લક્ષણો',
|
||||
name: 'નામ',
|
||||
unique: 'અદ્વિતીય',
|
||||
index_type: 'ઇન્ડેક્સ પ્રકાર',
|
||||
delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -177,14 +180,17 @@ export const gu: LanguageTranslation = {
|
||||
description: 'શરૂ કરવા માટે એક ટેબલ બનાવો',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'સંબંધો',
|
||||
refs_section: {
|
||||
refs: 'રેફ્સ',
|
||||
filter: 'ફિલ્ટર',
|
||||
add_relationship: 'સંબંધ ઉમેરો',
|
||||
collapse: 'બધાને સકુચિત કરો',
|
||||
add_relationship: 'સંબંધ ઉમેરો',
|
||||
relationships: 'સંબંધો',
|
||||
dependencies: 'નિર્ભરતાઓ',
|
||||
relationship: {
|
||||
relationship: 'સંબંધ',
|
||||
primary: 'પ્રાથમિક ટેબલ',
|
||||
foreign: 'સંદર્ભ ટેબલ',
|
||||
foreign: 'સંદર્ભિત ટેબલ',
|
||||
cardinality: 'કાર્ડિનાલિટી',
|
||||
delete_relationship: 'કાઢી નાખો',
|
||||
relationship_actions: {
|
||||
@@ -192,27 +198,19 @@ export const gu: LanguageTranslation = {
|
||||
delete_relationship: 'કાઢી નાખો',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'કોઈ સંબંધો નથી',
|
||||
description: 'ટેબલ્સ કનેક્ટ કરવા માટે એક સંબંધ બનાવો',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'નિર્ભરતાઓ',
|
||||
filter: 'ફિલ્ટર',
|
||||
collapse: 'સિકોડો',
|
||||
dependency: {
|
||||
dependency: 'નિર્ભરતા',
|
||||
table: 'ટેબલ',
|
||||
dependent_table: 'આધાર રાખેલું ટેબલ',
|
||||
delete_dependency: 'નિર્ભરતા કાઢી નાખો',
|
||||
dependent_table: 'નિર્ભરશીલ વ્યૂ',
|
||||
delete_dependency: 'કાઢી નાખો',
|
||||
dependency_actions: {
|
||||
title: 'ક્રિયાઓ',
|
||||
delete_dependency: 'નિર્ભરતા કાઢી નાખો',
|
||||
delete_dependency: 'કાઢી નાખો',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'કોઈ નિર્ભરતાઓ નથી',
|
||||
description: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.',
|
||||
title: 'કોઈ સંબંધો નથી',
|
||||
description: 'શરૂ કરવા માટે એક સંબંધ બનાવો',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -252,12 +250,16 @@ export const gu: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'કોઈ enum મૂલ્યો વ્યાખ્યાયિત નથી',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -271,7 +273,11 @@ export const gu: LanguageTranslation = {
|
||||
show_all: 'બધું બતાવો',
|
||||
undo: 'અનડુ',
|
||||
redo: 'રીડુ',
|
||||
reorder_diagram: 'ડાયાગ્રામ ફરીથી વ્યવસ્થિત કરો',
|
||||
reorder_diagram: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'ઓવરલેપ કરતો ટેબલ હાઇલાઇટ કરો',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -310,7 +316,7 @@ export const gu: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'ડાયાગ્રામ ખોલો',
|
||||
title: 'ડેટાબેસ ખોલો',
|
||||
description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.',
|
||||
table_columns: {
|
||||
name: 'નામ',
|
||||
@@ -320,6 +326,12 @@ export const gu: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'રદ કરો',
|
||||
open: 'ખોલો',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'ખોલો',
|
||||
duplicate: 'ડુપ્લિકેટ',
|
||||
delete: 'કાઢી નાખો',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -406,6 +418,14 @@ export const gu: LanguageTranslation = {
|
||||
confirm: 'બદલો',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'નવું સ્કીમા બનાવો',
|
||||
description:
|
||||
'હજી સુધી કોઈ સ્કીમા અસ્તિત્વમાં નથી. તમારા ટેબલ્સ ને વ્યવસ્થિત કરવા માટે તમારું પહેલું સ્કીમા બનાવો.',
|
||||
create: 'બનાવો',
|
||||
cancel: 'રદ કરો',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'અમને સુધારવામાં મદદ કરો!',
|
||||
description:
|
||||
@@ -461,6 +481,7 @@ export const gu: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'નવું ટેબલ',
|
||||
new_view: 'નવું વ્યૂ',
|
||||
new_relationship: 'નવો સંબંધ',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -482,6 +503,9 @@ export const gu: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'ભાષા બદલો',
|
||||
},
|
||||
|
||||
on: 'ચાલુ',
|
||||
off: 'બંધ',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const hi: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'नया',
|
||||
browse: 'ब्राउज़',
|
||||
tables: 'टेबल',
|
||||
refs: 'रेफ्स',
|
||||
areas: 'क्षेत्र',
|
||||
dependencies: 'निर्भरताएं',
|
||||
custom_types: 'कस्टम टाइप',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'फ़ाइल',
|
||||
new: 'नया',
|
||||
open: 'खोलें',
|
||||
actions: {
|
||||
actions: 'कार्य',
|
||||
new: 'नया...',
|
||||
browse: 'ब्राउज़ करें...',
|
||||
save: 'सहेजें',
|
||||
import: 'डेटाबेस आयात करें',
|
||||
export_sql: 'SQL निर्यात करें',
|
||||
export_as: 'के रूप में निर्यात करें',
|
||||
delete_diagram: 'आरेख हटाएँ',
|
||||
exit: 'बाहर जाएँ',
|
||||
delete_diagram: 'हटाएँ',
|
||||
},
|
||||
edit: {
|
||||
edit: 'संपादित करें',
|
||||
@@ -26,7 +34,10 @@ export const hi: LanguageTranslation = {
|
||||
hide_sidebar: 'साइडबार छिपाएँ',
|
||||
hide_cardinality: 'कार्डिनैलिटी छिपाएँ',
|
||||
show_cardinality: 'कार्डिनैलिटी दिखाएँ',
|
||||
hide_field_attributes: 'फ़ील्ड विशेषताएँ छिपाएँ',
|
||||
show_field_attributes: 'फ़ील्ड विशेषताएँ दिखाएँ',
|
||||
zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
|
||||
show_views: 'डेटाबेस व्यू',
|
||||
theme: 'थीम',
|
||||
show_dependencies: 'निर्भरता दिखाएँ',
|
||||
hide_dependencies: 'निर्भरता छिपाएँ',
|
||||
@@ -63,22 +74,13 @@ export const hi: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'आरेख पुनः व्यवस्थित करें',
|
||||
title: 'आरेख स्वचालित व्यवस्थित करें',
|
||||
description:
|
||||
'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?',
|
||||
reorder: 'पुनः व्यवस्थित करें',
|
||||
reorder: 'स्वचालित व्यवस्थित करें',
|
||||
cancel: 'रद्द करें',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'एकाधिक स्कीमा',
|
||||
description:
|
||||
'{{schemasCount}} स्कीमा इस आरेख में हैं। वर्तमान में प्रदर्शित: {{formattedSchemas}}।',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'कोई नहीं',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'कॉपी असफल',
|
||||
@@ -114,14 +116,11 @@ export const hi: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'स्कीमा:',
|
||||
filter_by_schema: 'स्कीमा द्वारा फ़िल्टर करें',
|
||||
search_schema: 'स्कीमा खोजें...',
|
||||
no_schemas_found: 'कोई स्कीमा नहीं मिला।',
|
||||
view_all_options: 'सभी विकल्प देखें...',
|
||||
tables_section: {
|
||||
tables: 'तालिकाएँ',
|
||||
add_table: 'तालिका जोड़ें',
|
||||
add_view: 'व्यू जोड़ें',
|
||||
filter: 'फ़िल्टर',
|
||||
collapse: 'सभी को संक्षिप्त करें',
|
||||
// TODO: Translate
|
||||
@@ -147,6 +146,7 @@ export const hi: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'फ़ील्ड विशेषताएँ',
|
||||
unique: 'अद्वितीय',
|
||||
auto_increment: 'ऑटो इंक्रीमेंट',
|
||||
comments: 'टिप्पणियाँ',
|
||||
no_comments: 'कोई टिप्पणी नहीं',
|
||||
delete_field: 'फ़ील्ड हटाएँ',
|
||||
@@ -155,11 +155,14 @@ export const hi: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Precision',
|
||||
scale: 'Scale',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'सूचकांक विशेषताएँ',
|
||||
name: 'नाम',
|
||||
unique: 'अद्वितीय',
|
||||
index_type: 'इंडेक्स प्रकार',
|
||||
delete_index: 'सूचकांक हटाएँ',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -176,12 +179,15 @@ export const hi: LanguageTranslation = {
|
||||
description: 'शुरू करने के लिए एक तालिका बनाएँ',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'संबंध',
|
||||
refs_section: {
|
||||
refs: 'रेफ्स',
|
||||
filter: 'फ़िल्टर',
|
||||
add_relationship: 'संबंध जोड़ें',
|
||||
collapse: 'सभी को संक्षिप्त करें',
|
||||
add_relationship: 'संबंध जोड़ें',
|
||||
relationships: 'संबंध',
|
||||
dependencies: 'निर्भरताएँ',
|
||||
relationship: {
|
||||
relationship: 'संबंध',
|
||||
primary: 'प्राथमिक तालिका',
|
||||
foreign: 'संदर्भित तालिका',
|
||||
cardinality: 'कार्डिनैलिटी',
|
||||
@@ -191,28 +197,19 @@ export const hi: LanguageTranslation = {
|
||||
delete_relationship: 'हटाएँ',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'कोई संबंध नहीं',
|
||||
description:
|
||||
'तालिकाओं को कनेक्ट करने के लिए एक संबंध बनाएँ',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'निर्भरताएँ',
|
||||
filter: 'फ़िल्टर',
|
||||
collapse: 'सिकोड़ें',
|
||||
dependency: {
|
||||
dependency: 'निर्भरता',
|
||||
table: 'तालिका',
|
||||
dependent_table: 'आश्रित तालिका',
|
||||
delete_dependency: 'निर्भरता हटाएँ',
|
||||
dependent_table: 'आश्रित दृश्य',
|
||||
delete_dependency: 'हटाएँ',
|
||||
dependency_actions: {
|
||||
title: 'कार्रवाइयाँ',
|
||||
delete_dependency: 'निर्भरता हटाएँ',
|
||||
title: 'क्रियाएँ',
|
||||
delete_dependency: 'हटाएँ',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'कोई निर्भरता नहीं',
|
||||
description: 'इस अनुभाग में कोई निर्भरता उपलब्ध नहीं है।',
|
||||
title: 'कोई संबंध नहीं',
|
||||
description: 'शुरू करने के लिए एक संबंध बनाएँ',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -252,12 +249,16 @@ export const hi: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'कोई enum मान परिभाषित नहीं',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -271,7 +272,11 @@ export const hi: LanguageTranslation = {
|
||||
show_all: 'सभी दिखाएँ',
|
||||
undo: 'पूर्ववत करें',
|
||||
redo: 'पुनः करें',
|
||||
reorder_diagram: 'आरेख पुनः व्यवस्थित करें',
|
||||
reorder_diagram: 'आरेख स्वचालित व्यवस्थित करें',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'ओवरलैपिंग तालिकाओं को हाइलाइट करें',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -313,7 +318,7 @@ export const hi: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'आरेख खोलें',
|
||||
title: 'डेटाबेस खोलें',
|
||||
description: 'नीचे दी गई सूची से एक आरेख चुनें।',
|
||||
table_columns: {
|
||||
name: 'नाम',
|
||||
@@ -323,6 +328,12 @@ export const hi: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'रद्द करें',
|
||||
open: 'खोलें',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'खोलें',
|
||||
duplicate: 'डुप्लिकेट',
|
||||
delete: 'हटाएं',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -409,6 +420,14 @@ export const hi: LanguageTranslation = {
|
||||
confirm: 'बदलें',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'नया स्कीमा बनाएं',
|
||||
description:
|
||||
'अभी तक कोई स्कीमा मौजूद नहीं है। अपनी तालिकाओं को व्यवस्थित करने के लिए अपना पहला स्कीमा बनाएं।',
|
||||
create: 'बनाएं',
|
||||
cancel: 'रद्द करें',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'हमें सुधारने में मदद करें!',
|
||||
description:
|
||||
@@ -464,6 +483,7 @@ export const hi: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'नई तालिका',
|
||||
new_view: 'नया व्यू',
|
||||
new_relationship: 'नया संबंध',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -486,6 +506,9 @@ export const hi: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'भाषा बदलें',
|
||||
},
|
||||
|
||||
on: 'चालू',
|
||||
off: 'बंद',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
509
src/i18n/locales/hr.ts
Normal file
509
src/i18n/locales/hr.ts
Normal file
@@ -0,0 +1,509 @@
|
||||
import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const hr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Novi',
|
||||
browse: 'Pregledaj',
|
||||
tables: 'Tablice',
|
||||
refs: 'Refs',
|
||||
areas: 'Područja',
|
||||
dependencies: 'Ovisnosti',
|
||||
custom_types: 'Prilagođeni Tipovi',
|
||||
},
|
||||
menu: {
|
||||
actions: {
|
||||
actions: 'Akcije',
|
||||
new: 'Novi...',
|
||||
browse: 'Pregledaj...',
|
||||
save: 'Spremi',
|
||||
import: 'Uvezi',
|
||||
export_sql: 'Izvezi SQL',
|
||||
export_as: 'Izvezi kao',
|
||||
delete_diagram: 'Izbriši',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Uredi',
|
||||
undo: 'Poništi',
|
||||
redo: 'Ponovi',
|
||||
clear: 'Očisti',
|
||||
},
|
||||
view: {
|
||||
view: 'Prikaz',
|
||||
show_sidebar: 'Prikaži bočnu traku',
|
||||
hide_sidebar: 'Sakrij bočnu traku',
|
||||
hide_cardinality: 'Sakrij kardinalnost',
|
||||
show_cardinality: 'Prikaži kardinalnost',
|
||||
hide_field_attributes: 'Sakrij atribute polja',
|
||||
show_field_attributes: 'Prikaži atribute polja',
|
||||
zoom_on_scroll: 'Zumiranje pri skrolanju',
|
||||
show_views: 'Pogledi Baze Podataka',
|
||||
theme: 'Tema',
|
||||
show_dependencies: 'Prikaži ovisnosti',
|
||||
hide_dependencies: 'Sakrij ovisnosti',
|
||||
show_minimap: 'Prikaži mini kartu',
|
||||
hide_minimap: 'Sakrij mini kartu',
|
||||
},
|
||||
backup: {
|
||||
backup: 'Sigurnosna kopija',
|
||||
export_diagram: 'Izvezi dijagram',
|
||||
restore_diagram: 'Vrati dijagram',
|
||||
},
|
||||
help: {
|
||||
help: 'Pomoć',
|
||||
docs_website: 'Dokumentacija',
|
||||
join_discord: 'Pridružite nam se na Discordu',
|
||||
},
|
||||
},
|
||||
|
||||
delete_diagram_alert: {
|
||||
title: 'Izbriši dijagram',
|
||||
description:
|
||||
'Ova radnja se ne može poništiti. Ovo će trajno izbrisati dijagram.',
|
||||
cancel: 'Odustani',
|
||||
delete: 'Izbriši',
|
||||
},
|
||||
|
||||
clear_diagram_alert: {
|
||||
title: 'Očisti dijagram',
|
||||
description:
|
||||
'Ova radnja se ne može poništiti. Ovo će trajno izbrisati sve podatke u dijagramu.',
|
||||
cancel: 'Odustani',
|
||||
clear: 'Očisti',
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Automatski preuredi dijagram',
|
||||
description:
|
||||
'Ova radnja će preurediti sve tablice u dijagramu. Želite li nastaviti?',
|
||||
reorder: 'Automatski preuredi',
|
||||
cancel: 'Odustani',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Kopiranje neuspješno',
|
||||
description: 'Međuspremnik nije podržan.',
|
||||
},
|
||||
failed: {
|
||||
title: 'Kopiranje neuspješno',
|
||||
description: 'Nešto je pošlo po zlu. Molimo pokušajte ponovno.',
|
||||
},
|
||||
},
|
||||
|
||||
theme: {
|
||||
system: 'Sustav',
|
||||
light: 'Svijetla',
|
||||
dark: 'Tamna',
|
||||
},
|
||||
|
||||
zoom: {
|
||||
on: 'Uključeno',
|
||||
off: 'Isključeno',
|
||||
},
|
||||
|
||||
last_saved: 'Zadnje spremljeno',
|
||||
saved: 'Spremljeno',
|
||||
loading_diagram: 'Učitavanje dijagrama...',
|
||||
deselect_all: 'Odznači sve',
|
||||
select_all: 'Označi sve',
|
||||
clear: 'Očisti',
|
||||
show_more: 'Prikaži više',
|
||||
show_less: 'Prikaži manje',
|
||||
copy_to_clipboard: 'Kopiraj u međuspremnik',
|
||||
copied: 'Kopirano!',
|
||||
|
||||
side_panel: {
|
||||
view_all_options: 'Prikaži sve opcije...',
|
||||
tables_section: {
|
||||
tables: 'Tablice',
|
||||
add_table: 'Dodaj tablicu',
|
||||
add_view: 'Dodaj Pogled',
|
||||
filter: 'Filtriraj',
|
||||
collapse: 'Sažmi sve',
|
||||
clear: 'Očisti filter',
|
||||
no_results:
|
||||
'Nema pronađenih tablica koje odgovaraju vašem filteru.',
|
||||
show_list: 'Prikaži popis tablica',
|
||||
show_dbml: 'Prikaži DBML uređivač',
|
||||
|
||||
table: {
|
||||
fields: 'Polja',
|
||||
nullable: 'Može biti null?',
|
||||
primary_key: 'Primarni ključ',
|
||||
indexes: 'Indeksi',
|
||||
comments: 'Komentari',
|
||||
no_comments: 'Nema komentara',
|
||||
add_field: 'Dodaj polje',
|
||||
add_index: 'Dodaj indeks',
|
||||
index_select_fields: 'Odaberi polja',
|
||||
no_types_found: 'Nema pronađenih tipova',
|
||||
field_name: 'Naziv',
|
||||
field_type: 'Tip',
|
||||
field_actions: {
|
||||
title: 'Atributi polja',
|
||||
unique: 'Jedinstven',
|
||||
auto_increment: 'Automatsko povećavanje',
|
||||
character_length: 'Maksimalna dužina',
|
||||
precision: 'Preciznost',
|
||||
scale: 'Skala',
|
||||
comments: 'Komentari',
|
||||
no_comments: 'Nema komentara',
|
||||
default_value: 'Zadana vrijednost',
|
||||
no_default: 'Nema zadane vrijednosti',
|
||||
delete_field: 'Izbriši polje',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Atributi indeksa',
|
||||
name: 'Naziv',
|
||||
unique: 'Jedinstven',
|
||||
index_type: 'Vrsta indeksa',
|
||||
delete_index: 'Izbriši indeks',
|
||||
},
|
||||
table_actions: {
|
||||
title: 'Radnje nad tablicom',
|
||||
change_schema: 'Promijeni shemu',
|
||||
add_field: 'Dodaj polje',
|
||||
add_index: 'Dodaj indeks',
|
||||
duplicate_table: 'Dupliciraj tablicu',
|
||||
delete_table: 'Izbriši tablicu',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Nema tablica',
|
||||
description: 'Stvorite tablicu za početak',
|
||||
},
|
||||
},
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Filtriraj',
|
||||
collapse: 'Sažmi sve',
|
||||
add_relationship: 'Dodaj vezu',
|
||||
relationships: 'Veze',
|
||||
dependencies: 'Ovisnosti',
|
||||
relationship: {
|
||||
relationship: 'Veza',
|
||||
primary: 'Primarna tablica',
|
||||
foreign: 'Referentna tablica',
|
||||
cardinality: 'Kardinalnost',
|
||||
delete_relationship: 'Izbriši',
|
||||
relationship_actions: {
|
||||
title: 'Radnje',
|
||||
delete_relationship: 'Izbriši',
|
||||
},
|
||||
},
|
||||
dependency: {
|
||||
dependency: 'Ovisnost',
|
||||
table: 'Tablica',
|
||||
dependent_table: 'Ovisni pogled',
|
||||
delete_dependency: 'Izbriši',
|
||||
dependency_actions: {
|
||||
title: 'Radnje',
|
||||
delete_dependency: 'Izbriši',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Nema veze',
|
||||
description: 'Stvorite vezu za početak',
|
||||
},
|
||||
},
|
||||
|
||||
areas_section: {
|
||||
areas: 'Područja',
|
||||
add_area: 'Dodaj područje',
|
||||
filter: 'Filtriraj',
|
||||
clear: 'Očisti filter',
|
||||
no_results:
|
||||
'Nema pronađenih područja koja odgovaraju vašem filteru.',
|
||||
|
||||
area: {
|
||||
area_actions: {
|
||||
title: 'Radnje nad područjem',
|
||||
edit_name: 'Uredi naziv',
|
||||
delete_area: 'Izbriši područje',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Nema područja',
|
||||
description: 'Stvorite područje za početak',
|
||||
},
|
||||
},
|
||||
|
||||
custom_types_section: {
|
||||
custom_types: 'Prilagođeni tipovi',
|
||||
filter: 'Filtriraj',
|
||||
clear: 'Očisti filter',
|
||||
no_results:
|
||||
'Nema pronađenih prilagođenih tipova koji odgovaraju vašem filteru.',
|
||||
empty_state: {
|
||||
title: 'Nema prilagođenih tipova',
|
||||
description:
|
||||
'Prilagođeni tipovi će se pojaviti ovdje kada budu dostupni u vašoj bazi podataka',
|
||||
},
|
||||
custom_type: {
|
||||
kind: 'Vrsta',
|
||||
enum_values: 'Enum vrijednosti',
|
||||
composite_fields: 'Polja',
|
||||
no_fields: 'Nema definiranih polja',
|
||||
no_values: 'Nema definiranih enum vrijednosti',
|
||||
field_name_placeholder: 'Naziv polja',
|
||||
field_type_placeholder: 'Odaberi tip',
|
||||
add_field: 'Dodaj polje',
|
||||
no_fields_tooltip:
|
||||
'Nema definiranih polja za ovaj prilagođeni tip',
|
||||
custom_type_actions: {
|
||||
title: 'Radnje',
|
||||
highlight_fields: 'Istakni polja',
|
||||
clear_field_highlight: 'Ukloni isticanje',
|
||||
delete_custom_type: 'Izbriši',
|
||||
},
|
||||
delete_custom_type: 'Izbriši tip',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
toolbar: {
|
||||
zoom_in: 'Uvećaj',
|
||||
zoom_out: 'Smanji',
|
||||
save: 'Spremi',
|
||||
show_all: 'Prikaži sve',
|
||||
undo: 'Poništi',
|
||||
redo: 'Ponovi',
|
||||
reorder_diagram: 'Automatski preuredi dijagram',
|
||||
highlight_overlapping_tables: 'Istakni preklapajuće tablice',
|
||||
clear_custom_type_highlight: 'Ukloni isticanje za "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Isticanje "{{typeName}}" - Kliknite za uklanjanje',
|
||||
filter: 'Filtriraj tablice',
|
||||
},
|
||||
|
||||
new_diagram_dialog: {
|
||||
database_selection: {
|
||||
title: 'Koja je vaša baza podataka?',
|
||||
description:
|
||||
'Svaka baza podataka ima svoje jedinstvene značajke i mogućnosti.',
|
||||
check_examples_long: 'Pogledaj primjere',
|
||||
check_examples_short: 'Primjeri',
|
||||
},
|
||||
|
||||
import_database: {
|
||||
title: 'Uvezite svoju bazu podataka',
|
||||
database_edition: 'Verzija baze podataka:',
|
||||
step_1: 'Pokrenite ovu skriptu u svojoj bazi podataka:',
|
||||
step_2: 'Zalijepite rezultat skripte u ovaj dio →',
|
||||
script_results_placeholder: 'Rezultati skripte ovdje...',
|
||||
ssms_instructions: {
|
||||
button_text: 'SSMS upute',
|
||||
title: 'Upute',
|
||||
step_1: 'Idite na Tools > Options > Query Results > SQL Server.',
|
||||
step_2: 'Ako koristite "Results to Grid," promijenite Maximum Characters Retrieved za Non-XML podatke (postavite na 9999999).',
|
||||
},
|
||||
instructions_link: 'Trebate pomoć? Pogledajte kako',
|
||||
check_script_result: 'Provjeri rezultat skripte',
|
||||
},
|
||||
|
||||
cancel: 'Odustani',
|
||||
import_from_file: 'Uvezi iz datoteke',
|
||||
back: 'Natrag',
|
||||
empty_diagram: 'Prazan dijagram',
|
||||
continue: 'Nastavi',
|
||||
import: 'Uvezi',
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Otvori bazu podataka',
|
||||
description: 'Odaberite dijagram za otvaranje iz popisa ispod.',
|
||||
table_columns: {
|
||||
name: 'Naziv',
|
||||
created_at: 'Stvoreno',
|
||||
last_modified: 'Zadnje izmijenjeno',
|
||||
tables_count: 'Tablice',
|
||||
},
|
||||
cancel: 'Odustani',
|
||||
open: 'Otvori',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Otvori',
|
||||
duplicate: 'Dupliciraj',
|
||||
delete: 'Obriši',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
title: 'Izvezi SQL',
|
||||
description:
|
||||
'Izvezite shemu vašeg dijagrama u {{databaseType}} skriptu',
|
||||
close: 'Zatvori',
|
||||
loading: {
|
||||
text: 'AI generira SQL za {{databaseType}}...',
|
||||
description: 'Ovo bi trebalo potrajati do 30 sekundi.',
|
||||
},
|
||||
error: {
|
||||
message:
|
||||
'Greška pri generiranju SQL skripte. Molimo pokušajte ponovno kasnije ili <0>kontaktirajte nas</0>.',
|
||||
description:
|
||||
'Slobodno koristite svoj OPENAI_TOKEN, pogledajte priručnik <0>ovdje</0>.',
|
||||
},
|
||||
},
|
||||
|
||||
create_relationship_dialog: {
|
||||
title: 'Kreiraj vezu',
|
||||
primary_table: 'Primarna tablica',
|
||||
primary_field: 'Primarno polje',
|
||||
referenced_table: 'Referentna tablica',
|
||||
referenced_field: 'Referentno polje',
|
||||
primary_table_placeholder: 'Odaberi tablicu',
|
||||
primary_field_placeholder: 'Odaberi polje',
|
||||
referenced_table_placeholder: 'Odaberi tablicu',
|
||||
referenced_field_placeholder: 'Odaberi polje',
|
||||
no_tables_found: 'Nema pronađenih tablica',
|
||||
no_fields_found: 'Nema pronađenih polja',
|
||||
create: 'Kreiraj',
|
||||
cancel: 'Odustani',
|
||||
},
|
||||
|
||||
import_database_dialog: {
|
||||
title: 'Uvezi u trenutni dijagram',
|
||||
override_alert: {
|
||||
title: 'Uvezi bazu podataka',
|
||||
content: {
|
||||
alert: 'Uvoz ovog dijagrama će utjecati na postojeće tablice i veze.',
|
||||
new_tables:
|
||||
'<bold>{{newTablesNumber}}</bold> novih tablica će biti dodano.',
|
||||
new_relationships:
|
||||
'<bold>{{newRelationshipsNumber}}</bold> novih veza će biti stvoreno.',
|
||||
tables_override:
|
||||
'<bold>{{tablesOverrideNumber}}</bold> tablica će biti prepisano.',
|
||||
proceed: 'Želite li nastaviti?',
|
||||
},
|
||||
import: 'Uvezi',
|
||||
cancel: 'Odustani',
|
||||
},
|
||||
},
|
||||
|
||||
export_image_dialog: {
|
||||
title: 'Izvezi sliku',
|
||||
description: 'Odaberite faktor veličine za izvoz:',
|
||||
scale_1x: '1x Obično',
|
||||
scale_2x: '2x (Preporučeno)',
|
||||
scale_3x: '3x',
|
||||
scale_4x: '4x',
|
||||
cancel: 'Odustani',
|
||||
export: 'Izvezi',
|
||||
advanced_options: 'Napredne opcije',
|
||||
pattern: 'Uključi pozadinski uzorak',
|
||||
pattern_description: 'Dodaj suptilni mrežni uzorak u pozadinu.',
|
||||
transparent: 'Prozirna pozadina',
|
||||
transparent_description: 'Ukloni boju pozadine iz slike.',
|
||||
},
|
||||
|
||||
new_table_schema_dialog: {
|
||||
title: 'Odaberi shemu',
|
||||
description:
|
||||
'Trenutno je prikazano više shema. Odaberite jednu za novu tablicu.',
|
||||
cancel: 'Odustani',
|
||||
confirm: 'Potvrdi',
|
||||
},
|
||||
|
||||
update_table_schema_dialog: {
|
||||
title: 'Promijeni shemu',
|
||||
description: 'Ažuriraj shemu tablice "{{tableName}}"',
|
||||
cancel: 'Odustani',
|
||||
confirm: 'Promijeni',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Stvori novu shemu',
|
||||
description:
|
||||
'Još ne postoje sheme. Stvorite svoju prvu shemu za organiziranje tablica.',
|
||||
create: 'Stvori',
|
||||
cancel: 'Odustani',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Pomozite nam da se poboljšamo!',
|
||||
description:
|
||||
'Želite li nam dati zvjezdicu na GitHubu? Samo je jedan klik!',
|
||||
close: 'Ne sada',
|
||||
confirm: 'Naravno!',
|
||||
},
|
||||
export_diagram_dialog: {
|
||||
title: 'Izvezi dijagram',
|
||||
description: 'Odaberite format za izvoz:',
|
||||
format_json: 'JSON',
|
||||
cancel: 'Odustani',
|
||||
export: 'Izvezi',
|
||||
error: {
|
||||
title: 'Greška pri izvozu dijagrama',
|
||||
description:
|
||||
'Nešto je pošlo po zlu. Trebate pomoć? support@chartdb.io',
|
||||
},
|
||||
},
|
||||
|
||||
import_diagram_dialog: {
|
||||
title: 'Uvezi dijagram',
|
||||
description: 'Uvezite dijagram iz JSON datoteke.',
|
||||
cancel: 'Odustani',
|
||||
import: 'Uvezi',
|
||||
error: {
|
||||
title: 'Greška pri uvozu dijagrama',
|
||||
description:
|
||||
'JSON dijagrama je nevažeći. Molimo provjerite JSON i pokušajte ponovno. Trebate pomoć? support@chartdb.io',
|
||||
},
|
||||
},
|
||||
|
||||
import_dbml_dialog: {
|
||||
example_title: 'Uvezi primjer DBML-a',
|
||||
title: 'Uvezi DBML',
|
||||
description: 'Uvezite shemu baze podataka iz DBML formata.',
|
||||
import: 'Uvezi',
|
||||
cancel: 'Odustani',
|
||||
skip_and_empty: 'Preskoči i isprazni',
|
||||
show_example: 'Prikaži primjer',
|
||||
error: {
|
||||
title: 'Greška pri uvozu DBML-a',
|
||||
description:
|
||||
'Neuspješno parsiranje DBML-a. Molimo provjerite sintaksu.',
|
||||
},
|
||||
},
|
||||
relationship_type: {
|
||||
one_to_one: 'Jedan na jedan',
|
||||
one_to_many: 'Jedan na više',
|
||||
many_to_one: 'Više na jedan',
|
||||
many_to_many: 'Više na više',
|
||||
},
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Nova tablica',
|
||||
new_view: 'Novi Pogled',
|
||||
new_relationship: 'Nova veza',
|
||||
new_area: 'Novo područje',
|
||||
},
|
||||
|
||||
table_node_context_menu: {
|
||||
edit_table: 'Uredi tablicu',
|
||||
duplicate_table: 'Dupliciraj tablicu',
|
||||
delete_table: 'Izbriši tablicu',
|
||||
add_relationship: 'Dodaj vezu',
|
||||
},
|
||||
|
||||
snap_to_grid_tooltip: 'Priljepljivanje na mrežu (Drži {{key}})',
|
||||
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Dvostruki klik za uređivanje',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Jezik',
|
||||
},
|
||||
|
||||
on: 'Uključeno',
|
||||
off: 'Isključeno',
|
||||
},
|
||||
};
|
||||
|
||||
export const hrMetadata: LanguageMetadata = {
|
||||
name: 'Croatian',
|
||||
nativeName: 'Hrvatski',
|
||||
code: 'hr',
|
||||
};
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const id_ID: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Baru',
|
||||
browse: 'Jelajahi',
|
||||
tables: 'Tabel',
|
||||
refs: 'Refs',
|
||||
areas: 'Area',
|
||||
dependencies: 'Ketergantungan',
|
||||
custom_types: 'Tipe Kustom',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Berkas',
|
||||
new: 'Buat Baru',
|
||||
open: 'Buka',
|
||||
actions: {
|
||||
actions: 'Aksi',
|
||||
new: 'Baru...',
|
||||
browse: 'Jelajahi...',
|
||||
save: 'Simpan',
|
||||
import: 'Impor Database',
|
||||
export_sql: 'Ekspor SQL',
|
||||
export_as: 'Ekspor Sebagai',
|
||||
delete_diagram: 'Hapus Diagram',
|
||||
exit: 'Keluar',
|
||||
delete_diagram: 'Hapus',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Ubah',
|
||||
@@ -26,7 +34,10 @@ export const id_ID: LanguageTranslation = {
|
||||
hide_sidebar: 'Sembunyikan Sidebar',
|
||||
hide_cardinality: 'Sembunyikan Kardinalitas',
|
||||
show_cardinality: 'Tampilkan Kardinalitas',
|
||||
hide_field_attributes: 'Sembunyikan Atribut Kolom',
|
||||
show_field_attributes: 'Tampilkan Atribut Kolom',
|
||||
zoom_on_scroll: 'Perbesar saat Scroll',
|
||||
show_views: 'Tampilan Database',
|
||||
theme: 'Tema',
|
||||
show_dependencies: 'Tampilkan Dependensi',
|
||||
hide_dependencies: 'Sembunyikan Dependensi',
|
||||
@@ -63,22 +74,13 @@ export const id_ID: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Atur Ulang Diagram',
|
||||
title: 'Atur Otomatis Diagram',
|
||||
description:
|
||||
'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?',
|
||||
reorder: 'Atur Ulang',
|
||||
reorder: 'Atur Otomatis',
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Gagal menyalin',
|
||||
@@ -113,14 +115,11 @@ export const id_ID: LanguageTranslation = {
|
||||
copied: 'Tersalin!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tabel',
|
||||
add_table: 'Tambah Tabel',
|
||||
add_view: 'Tambah Tampilan',
|
||||
filter: 'Saring',
|
||||
collapse: 'Lipat Semua',
|
||||
// TODO: Translate
|
||||
@@ -146,6 +145,7 @@ export const id_ID: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Atribut Kolom',
|
||||
unique: 'Unik',
|
||||
auto_increment: 'Kenaikan Otomatis',
|
||||
comments: 'Komentar',
|
||||
no_comments: 'Tidak ada komentar',
|
||||
delete_field: 'Hapus Kolom',
|
||||
@@ -154,11 +154,14 @@ export const id_ID: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Presisi',
|
||||
scale: 'Skala',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Atribut Indeks',
|
||||
name: 'Nama',
|
||||
unique: 'Unik',
|
||||
index_type: 'Tipe Indeks',
|
||||
delete_index: 'Hapus Indeks',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -175,12 +178,15 @@ export const id_ID: LanguageTranslation = {
|
||||
description: 'Buat tabel untuk memulai',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Hubungan',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Saring',
|
||||
add_relationship: 'Tambah Hubungan',
|
||||
collapse: 'Lipat Semua',
|
||||
add_relationship: 'Tambah Hubungan',
|
||||
relationships: 'Hubungan',
|
||||
dependencies: 'Dependensi',
|
||||
relationship: {
|
||||
relationship: 'Hubungan',
|
||||
primary: 'Tabel Primer',
|
||||
foreign: 'Tabel Referensi',
|
||||
cardinality: 'Kardinalitas',
|
||||
@@ -190,16 +196,8 @@ export const id_ID: LanguageTranslation = {
|
||||
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: 'Dependensi',
|
||||
table: 'Tabel',
|
||||
dependent_table: 'Tampilan Dependen',
|
||||
delete_dependency: 'Hapus',
|
||||
@@ -209,8 +207,8 @@ export const id_ID: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Tidak ada dependensi',
|
||||
description: 'Buat tampilan untuk memulai',
|
||||
title: 'Tidak ada hubungan',
|
||||
description: 'Buat hubungan untuk memulai',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -250,12 +248,16 @@ export const id_ID: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'Tidak ada nilai enum yang ditentukan',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -269,7 +271,11 @@ export const id_ID: LanguageTranslation = {
|
||||
show_all: 'Tampilkan Semua',
|
||||
undo: 'Undo',
|
||||
redo: 'Redo',
|
||||
reorder_diagram: 'Atur Ulang Diagram',
|
||||
reorder_diagram: 'Atur Otomatis Diagram',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Sorot Tabel yang Tumpang Tindih',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -309,7 +315,7 @@ export const id_ID: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Buka Diagram',
|
||||
title: 'Buka Database',
|
||||
description: 'Pilih diagram untuk dibuka dari daftar di bawah.',
|
||||
table_columns: {
|
||||
name: 'Name',
|
||||
@@ -319,6 +325,12 @@ export const id_ID: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Batal',
|
||||
open: 'Buka',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Buka',
|
||||
duplicate: 'Duplikat',
|
||||
delete: 'Hapus',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -404,6 +416,14 @@ export const id_ID: LanguageTranslation = {
|
||||
confirm: 'Ubah',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Buat Skema Baru',
|
||||
description:
|
||||
'Belum ada skema yang tersedia. Buat skema pertama Anda untuk mengatur tabel-tabel Anda.',
|
||||
create: 'Buat',
|
||||
cancel: 'Batal',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Bantu kami meningkatkan!',
|
||||
description:
|
||||
@@ -460,6 +480,7 @@ export const id_ID: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Tabel Baru',
|
||||
new_view: 'Tampilan Baru',
|
||||
new_relationship: 'Hubungan Baru',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -481,6 +502,9 @@ export const id_ID: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Bahasa',
|
||||
},
|
||||
|
||||
on: 'Aktif',
|
||||
off: 'Nonaktif',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ja: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '新規',
|
||||
browse: '参照',
|
||||
tables: 'テーブル',
|
||||
refs: '参照',
|
||||
areas: 'エリア',
|
||||
dependencies: '依存関係',
|
||||
custom_types: 'カスタムタイプ',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ファイル',
|
||||
new: '新規',
|
||||
open: '開く',
|
||||
actions: {
|
||||
actions: 'アクション',
|
||||
new: '新規...',
|
||||
browse: '参照...',
|
||||
save: '保存',
|
||||
import: 'データベースをインポート',
|
||||
export_sql: 'SQLをエクスポート',
|
||||
export_as: '形式を指定してエクスポート',
|
||||
delete_diagram: 'ダイアグラムを削除',
|
||||
exit: '終了',
|
||||
delete_diagram: '削除',
|
||||
},
|
||||
edit: {
|
||||
edit: '編集',
|
||||
@@ -26,7 +34,10 @@ export const ja: LanguageTranslation = {
|
||||
hide_sidebar: 'サイドバーを非表示',
|
||||
hide_cardinality: 'カーディナリティを非表示',
|
||||
show_cardinality: 'カーディナリティを表示',
|
||||
hide_field_attributes: 'フィールド属性を非表示',
|
||||
show_field_attributes: 'フィールド属性を表示',
|
||||
zoom_on_scroll: 'スクロールでズーム',
|
||||
show_views: 'データベースビュー',
|
||||
theme: 'テーマ',
|
||||
// TODO: Translate
|
||||
show_dependencies: 'Show Dependencies',
|
||||
@@ -65,22 +76,13 @@ export const ja: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'ダイアグラムを並べ替え',
|
||||
title: 'ダイアグラムを自動配置',
|
||||
description:
|
||||
'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?',
|
||||
reorder: '並べ替え',
|
||||
reorder: '自動配置',
|
||||
cancel: 'キャンセル',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '複数のスキーマ',
|
||||
description:
|
||||
'このダイアグラムには{{schemasCount}}個のスキーマがあります。現在表示中: {{formattedSchemas}}。',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'なし',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'コピー失敗',
|
||||
@@ -117,14 +119,11 @@ export const ja: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'スキーマ:',
|
||||
filter_by_schema: 'スキーマでフィルタ',
|
||||
search_schema: 'スキーマを検索...',
|
||||
no_schemas_found: 'スキーマが見つかりません。',
|
||||
view_all_options: 'すべてのオプションを表示...',
|
||||
tables_section: {
|
||||
tables: 'テーブル',
|
||||
add_table: 'テーブルを追加',
|
||||
add_view: 'ビューを追加',
|
||||
filter: 'フィルタ',
|
||||
collapse: 'すべて折りたたむ',
|
||||
// TODO: Translate
|
||||
@@ -150,6 +149,7 @@ export const ja: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'フィールド属性',
|
||||
unique: 'ユニーク',
|
||||
auto_increment: 'オートインクリメント',
|
||||
comments: 'コメント',
|
||||
no_comments: 'コメントがありません',
|
||||
delete_field: 'フィールドを削除',
|
||||
@@ -158,11 +158,14 @@ export const ja: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: '精度',
|
||||
scale: '小数点以下桁数',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'インデックス属性',
|
||||
name: '名前',
|
||||
unique: 'ユニーク',
|
||||
index_type: 'インデックスタイプ',
|
||||
delete_index: 'インデックスを削除',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -179,12 +182,15 @@ export const ja: LanguageTranslation = {
|
||||
description: 'テーブルを作成して開始してください',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'リレーションシップ',
|
||||
refs_section: {
|
||||
refs: '参照',
|
||||
filter: 'フィルタ',
|
||||
add_relationship: 'リレーションシップを追加',
|
||||
collapse: 'すべて折りたたむ',
|
||||
add_relationship: 'リレーションシップを追加',
|
||||
relationships: 'リレーションシップ',
|
||||
dependencies: '依存関係',
|
||||
relationship: {
|
||||
relationship: 'リレーションシップ',
|
||||
primary: '主テーブル',
|
||||
foreign: '参照テーブル',
|
||||
cardinality: 'カーディナリティ',
|
||||
@@ -194,29 +200,20 @@ export const ja: LanguageTranslation = {
|
||||
delete_relationship: '削除',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'リレーションシップがありません',
|
||||
description:
|
||||
'テーブルを接続するためにリレーションシップを作成してください',
|
||||
},
|
||||
},
|
||||
// TODO: Translate
|
||||
dependencies_section: {
|
||||
dependencies: 'Dependencies',
|
||||
filter: 'Filter',
|
||||
collapse: 'Collapse All',
|
||||
dependency: {
|
||||
table: 'Table',
|
||||
dependent_table: 'Dependent View',
|
||||
delete_dependency: 'Delete',
|
||||
dependency: '依存関係',
|
||||
table: 'テーブル',
|
||||
dependent_table: '依存ビュー',
|
||||
delete_dependency: '削除',
|
||||
dependency_actions: {
|
||||
title: 'Actions',
|
||||
delete_dependency: 'Delete',
|
||||
title: '操作',
|
||||
delete_dependency: '削除',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'No dependencies',
|
||||
description: 'Create a view to get started',
|
||||
title: 'リレーションシップがありません',
|
||||
description:
|
||||
'開始するためにリレーションシップを作成してください',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -256,12 +253,16 @@ export const ja: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: '列挙値が定義されていません',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -275,10 +276,12 @@ export const ja: LanguageTranslation = {
|
||||
show_all: 'すべて表示',
|
||||
undo: '元に戻す',
|
||||
redo: 'やり直し',
|
||||
reorder_diagram: 'ダイアグラムを並べ替え',
|
||||
reorder_diagram: 'ダイアグラムを自動配置',
|
||||
// TODO: Translate
|
||||
highlight_overlapping_tables: 'Highlight Overlapping Tables',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear', // TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
},
|
||||
|
||||
@@ -317,7 +320,7 @@ export const ja: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'ダイアグラムを開く',
|
||||
title: 'データベースを開く',
|
||||
description: '以下のリストからダイアグラムを選択してください。',
|
||||
table_columns: {
|
||||
name: '名前',
|
||||
@@ -327,6 +330,12 @@ export const ja: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'キャンセル',
|
||||
open: '開く',
|
||||
|
||||
diagram_actions: {
|
||||
open: '開く',
|
||||
duplicate: '複製',
|
||||
delete: '削除',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -413,6 +422,14 @@ export const ja: LanguageTranslation = {
|
||||
confirm: '変更',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: '新しいスキーマを作成',
|
||||
description:
|
||||
'スキーマがまだ存在しません。テーブルを整理するために最初のスキーマを作成してください。',
|
||||
create: '作成',
|
||||
cancel: 'キャンセル',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: '改善をサポートしてください!',
|
||||
description:
|
||||
@@ -468,6 +485,7 @@ export const ja: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: '新しいテーブル',
|
||||
new_view: '新しいビュー',
|
||||
new_relationship: '新しいリレーションシップ',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -490,6 +508,9 @@ export const ja: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: '言語',
|
||||
},
|
||||
|
||||
on: 'オン',
|
||||
off: 'オフ',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ko_KR: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '새로 만들기',
|
||||
browse: '찾아보기',
|
||||
tables: '테이블',
|
||||
refs: 'Refs',
|
||||
areas: '영역',
|
||||
dependencies: '종속성',
|
||||
custom_types: '사용자 지정 타입',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: '파일',
|
||||
new: '새 다이어그램',
|
||||
open: '열기',
|
||||
actions: {
|
||||
actions: '작업',
|
||||
new: '새로 만들기...',
|
||||
browse: '찾아보기...',
|
||||
save: '저장',
|
||||
import: '데이터베이스 가져오기',
|
||||
export_sql: 'SQL로 저장',
|
||||
export_as: '다른 형식으로 저장',
|
||||
delete_diagram: '다이어그램 삭제',
|
||||
exit: '종료',
|
||||
delete_diagram: '삭제',
|
||||
},
|
||||
edit: {
|
||||
edit: '편집',
|
||||
@@ -26,7 +34,10 @@ export const ko_KR: LanguageTranslation = {
|
||||
hide_sidebar: '사이드바 숨기기',
|
||||
hide_cardinality: '카디널리티 숨기기',
|
||||
show_cardinality: '카디널리티 보이기',
|
||||
hide_field_attributes: '필드 속성 숨기기',
|
||||
show_field_attributes: '필드 속성 보이기',
|
||||
zoom_on_scroll: '스크롤 시 확대',
|
||||
show_views: '데이터베이스 뷰',
|
||||
theme: '테마',
|
||||
show_dependencies: '종속성 보이기',
|
||||
hide_dependencies: '종속성 숨기기',
|
||||
@@ -63,22 +74,13 @@ export const ko_KR: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: '다이어그램 재정렬',
|
||||
title: '다이어그램 자동 정렬',
|
||||
description:
|
||||
'이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?',
|
||||
reorder: '재정렬',
|
||||
reorder: '자동 정렬',
|
||||
cancel: '취소',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '다중 스키마',
|
||||
description:
|
||||
'현재 다이어그램에 {{schemasCount}}개의 스키마가 있습니다. Currently displaying: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: '없음',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '복사 실패',
|
||||
@@ -113,14 +115,11 @@ export const ko_KR: LanguageTranslation = {
|
||||
copied: '복사됨!',
|
||||
|
||||
side_panel: {
|
||||
schema: '스키마:',
|
||||
filter_by_schema: '스키마로 필터링',
|
||||
search_schema: '스키마 검색...',
|
||||
no_schemas_found: '스키마를 찾을 수 없습니다.',
|
||||
view_all_options: '전체 옵션 보기...',
|
||||
tables_section: {
|
||||
tables: '테이블',
|
||||
add_table: '테이블 추가',
|
||||
add_view: '뷰 추가',
|
||||
filter: '필터',
|
||||
collapse: '모두 접기',
|
||||
// TODO: Translate
|
||||
@@ -146,6 +145,7 @@ export const ko_KR: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: '필드 속성',
|
||||
unique: '유니크 여부',
|
||||
auto_increment: '자동 증가',
|
||||
comments: '주석',
|
||||
no_comments: '주석 없음',
|
||||
delete_field: '필드 삭제',
|
||||
@@ -154,11 +154,14 @@ export const ko_KR: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: '정밀도',
|
||||
scale: '소수점 자릿수',
|
||||
},
|
||||
index_actions: {
|
||||
title: '인덱스 속성',
|
||||
name: '인덱스 명',
|
||||
unique: '유니크 여부',
|
||||
index_type: '인덱스 타입',
|
||||
delete_index: '인덱스 삭제',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -175,12 +178,15 @@ export const ko_KR: LanguageTranslation = {
|
||||
description: '테이블을 만들어 시작하세요.',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: '연관 관계',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: '필터',
|
||||
add_relationship: '연관 관계 추가',
|
||||
collapse: '모두 접기',
|
||||
add_relationship: '연관 관계 추가',
|
||||
relationships: '연관 관계',
|
||||
dependencies: '종속성',
|
||||
relationship: {
|
||||
relationship: '연관 관계',
|
||||
primary: '주 테이블',
|
||||
foreign: '참조 테이블',
|
||||
cardinality: '카디널리티',
|
||||
@@ -190,16 +196,8 @@ export const ko_KR: LanguageTranslation = {
|
||||
delete_relationship: '연관 관계 삭제',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: '연관 관계',
|
||||
description: '테이블 연결을 위해 연관 관계를 생성하세요',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: '종속성',
|
||||
filter: '필터',
|
||||
collapse: '모두 접기',
|
||||
dependency: {
|
||||
dependency: '종속성',
|
||||
table: '테이블',
|
||||
dependent_table: '뷰 테이블',
|
||||
delete_dependency: '삭제',
|
||||
@@ -209,8 +207,8 @@ export const ko_KR: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: '뷰 테이블 없음',
|
||||
description: '뷰 테이블을 만들어 시작하세요.',
|
||||
title: '연관 관계 없음',
|
||||
description: '연관 관계를 만들어 시작하세요.',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -250,12 +248,16 @@ export const ko_KR: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: '정의된 열거형 값이 없습니다',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -269,7 +271,11 @@ export const ko_KR: LanguageTranslation = {
|
||||
show_all: '전체 저장',
|
||||
undo: '실행 취소',
|
||||
redo: '다시 실행',
|
||||
reorder_diagram: '다이어그램 재정렬',
|
||||
reorder_diagram: '다이어그램 자동 정렬',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: '겹치는 테이블 강조 표시',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -309,7 +315,7 @@ export const ko_KR: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: '다이어그램 열기',
|
||||
title: '데이터베이스 열기',
|
||||
description: '아래의 목록에서 다이어그램을 선택하세요.',
|
||||
table_columns: {
|
||||
name: '이름',
|
||||
@@ -319,6 +325,12 @@ export const ko_KR: LanguageTranslation = {
|
||||
},
|
||||
cancel: '취소',
|
||||
open: '열기',
|
||||
|
||||
diagram_actions: {
|
||||
open: '열기',
|
||||
duplicate: '복제',
|
||||
delete: '삭제',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -404,6 +416,14 @@ export const ko_KR: LanguageTranslation = {
|
||||
confirm: '변경',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: '새 스키마 생성',
|
||||
description:
|
||||
'아직 스키마가 없습니다. 테이블을 정리하기 위해 첫 번째 스키마를 생성하세요.',
|
||||
create: '생성',
|
||||
cancel: '취소',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: '개선할 수 있도록 도와주세요!',
|
||||
description:
|
||||
@@ -457,6 +477,7 @@ export const ko_KR: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: '새 테이블',
|
||||
new_view: '새 뷰',
|
||||
new_relationship: '새 연관관계',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -478,6 +499,9 @@ export const ko_KR: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: '언어',
|
||||
},
|
||||
|
||||
on: '켜기',
|
||||
off: '끄기',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const mr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'नवीन',
|
||||
browse: 'ब्राउज',
|
||||
tables: 'टेबल',
|
||||
refs: 'Refs',
|
||||
areas: 'क्षेत्रे',
|
||||
dependencies: 'अवलंबने',
|
||||
custom_types: 'कस्टम प्रकार',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'फाइल',
|
||||
new: 'नवीन',
|
||||
open: 'उघडा',
|
||||
actions: {
|
||||
actions: 'क्रिया',
|
||||
new: 'नवीन...',
|
||||
browse: 'ब्राउज करा...',
|
||||
save: 'जतन करा',
|
||||
import: 'डेटाबेस इम्पोर्ट करा',
|
||||
export_sql: 'SQL एक्स्पोर्ट करा',
|
||||
export_as: 'म्हणून एक्स्पोर्ट करा',
|
||||
delete_diagram: 'आरेख हटवा',
|
||||
exit: 'बाहेर पडा',
|
||||
delete_diagram: 'हटवा',
|
||||
},
|
||||
edit: {
|
||||
edit: 'संपादन करा',
|
||||
@@ -26,7 +34,10 @@ export const mr: LanguageTranslation = {
|
||||
hide_sidebar: 'साइडबार लपवा',
|
||||
hide_cardinality: 'कार्डिनॅलिटी लपवा',
|
||||
show_cardinality: 'कार्डिनॅलिटी दाखवा',
|
||||
hide_field_attributes: 'फील्ड गुणधर्म लपवा',
|
||||
show_field_attributes: 'फील्ड गुणधर्म दाखवा',
|
||||
zoom_on_scroll: 'स्क्रोलवर झूम करा',
|
||||
show_views: 'डेटाबेस व्ह्यूज',
|
||||
theme: 'थीम',
|
||||
show_dependencies: 'डिपेंडेन्सि दाखवा',
|
||||
hide_dependencies: 'डिपेंडेन्सि लपवा',
|
||||
@@ -64,22 +75,13 @@ export const mr: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'आरेख पुनःक्रमित करा',
|
||||
title: 'आरेख स्वयंचलित व्यवस्थित करा',
|
||||
description:
|
||||
'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?',
|
||||
reorder: 'पुनःक्रमित करा',
|
||||
reorder: 'स्वयंचलित व्यवस्थित करा',
|
||||
cancel: 'रद्द करा',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'एकाधिक स्कीमा',
|
||||
description:
|
||||
'{{schemasCount}} स्कीमा या आरेखात आहेत. सध्या दाखवत आहोत: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'काहीही नाही',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'कॉपी अयशस्वी',
|
||||
@@ -116,14 +118,11 @@ export const mr: LanguageTranslation = {
|
||||
copied: 'Copied!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'स्कीमा:',
|
||||
filter_by_schema: 'स्कीमा द्वारे फिल्टर करा',
|
||||
search_schema: 'स्कीमा शोधा...',
|
||||
no_schemas_found: 'कोणतेही स्कीमा सापडले नाहीत.',
|
||||
view_all_options: 'सर्व पर्याय पहा...',
|
||||
tables_section: {
|
||||
tables: 'टेबल्स',
|
||||
add_table: 'टेबल जोडा',
|
||||
add_view: 'व्ह्यू जोडा',
|
||||
filter: 'फिल्टर',
|
||||
collapse: 'सर्व संकुचित करा',
|
||||
// TODO: Translate
|
||||
@@ -149,6 +148,7 @@ export const mr: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'फील्ड गुणधर्म',
|
||||
unique: 'युनिक',
|
||||
auto_increment: 'ऑटो इंक्रिमेंट',
|
||||
comments: 'टिप्पण्या',
|
||||
no_comments: 'कोणत्याही टिप्पणी नाहीत',
|
||||
delete_field: 'फील्ड हटवा',
|
||||
@@ -157,11 +157,14 @@ export const mr: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'अचूकता',
|
||||
scale: 'प्रमाण',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'इंडेक्स गुणधर्म',
|
||||
name: 'नाव',
|
||||
unique: 'युनिक',
|
||||
index_type: 'इंडेक्स प्रकार',
|
||||
delete_index: 'इंडेक्स हटवा',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -179,12 +182,15 @@ export const mr: LanguageTranslation = {
|
||||
description: 'सुरू करण्यासाठी एक टेबल तयार करा',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'रिलेशनशिप',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'फिल्टर',
|
||||
add_relationship: 'रिलेशनशिप जोडा',
|
||||
collapse: 'सर्व संकुचित करा',
|
||||
add_relationship: 'रिलेशनशिप जोडा',
|
||||
relationships: 'रिलेशनशिप',
|
||||
dependencies: 'डिपेंडेन्सि',
|
||||
relationship: {
|
||||
relationship: 'रिलेशनशिप',
|
||||
primary: 'प्राथमिक टेबल',
|
||||
foreign: 'रेफरंस टेबल',
|
||||
cardinality: 'कार्डिनॅलिटी',
|
||||
@@ -194,17 +200,8 @@ export const mr: LanguageTranslation = {
|
||||
delete_relationship: 'हटवा',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'कोणतेही रिलेशनशिप नाहीत',
|
||||
description:
|
||||
'टेबल्स कनेक्ट करण्यासाठी एक रिलेशनशिप तयार करा',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'डिपेंडेन्सि',
|
||||
filter: 'फिल्टर',
|
||||
collapse: 'सर्व संकुचित करा',
|
||||
dependency: {
|
||||
dependency: 'डिपेंडेन्सि',
|
||||
table: 'टेबल',
|
||||
dependent_table: 'डिपेंडेन्सि दृश्य',
|
||||
delete_dependency: 'हटवा',
|
||||
@@ -214,8 +211,8 @@ export const mr: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'कोणत्याही डिपेंडेन्सि नाहीत',
|
||||
description: 'सुरू करण्यासाठी एक दृश्य तयार करा',
|
||||
title: 'कोणतेही रिलेशनशिप नाहीत',
|
||||
description: 'सुरू करण्यासाठी एक रिलेशनशिप तयार करा',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -255,12 +252,16 @@ export const mr: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'कोणतीही enum मूल्ये परिभाषित नाहीत',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -274,7 +275,11 @@ export const mr: LanguageTranslation = {
|
||||
show_all: 'सर्व दाखवा',
|
||||
undo: 'पूर्ववत करा',
|
||||
redo: 'पुन्हा करा',
|
||||
reorder_diagram: 'आरेख पुनःक्रमित करा',
|
||||
reorder_diagram: 'आरेख स्वयंचलित व्यवस्थित करा',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'ओव्हरलॅपिंग टेबल्स हायलाइट करा',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -316,7 +321,7 @@ export const mr: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'आरेख उघडा',
|
||||
title: 'डेटाबेस उघडा',
|
||||
description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.',
|
||||
table_columns: {
|
||||
name: 'नाव',
|
||||
@@ -326,6 +331,12 @@ export const mr: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'रद्द करा',
|
||||
open: 'उघडा',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'उघडा',
|
||||
duplicate: 'डुप्लिकेट',
|
||||
delete: 'हटवा',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -412,6 +423,14 @@ export const mr: LanguageTranslation = {
|
||||
confirm: 'बदला',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'नवीन स्कीमा तयार करा',
|
||||
description:
|
||||
'अजून कोणतीही स्कीमा अस्तित्वात नाही. आपल्या टेबल्स व्यवस्थित करण्यासाठी आपली पहिली स्कीमा तयार करा.',
|
||||
create: 'तयार करा',
|
||||
cancel: 'रद्द करा',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'आम्हाला सुधारण्यास मदत करा!',
|
||||
description:
|
||||
@@ -470,6 +489,7 @@ export const mr: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'नवीन टेबल',
|
||||
new_view: 'नवीन व्ह्यू',
|
||||
new_relationship: 'नवीन रिलेशनशिप',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -493,6 +513,9 @@ export const mr: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'भाषा बदला',
|
||||
},
|
||||
|
||||
on: 'चालू',
|
||||
off: 'बंद',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ne: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'नयाँ',
|
||||
browse: 'ब्राउज',
|
||||
tables: 'टेबलहरू',
|
||||
refs: 'Refs',
|
||||
areas: 'क्षेत्रहरू',
|
||||
dependencies: 'निर्भरताहरू',
|
||||
custom_types: 'कस्टम प्रकारहरू',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'फाइल',
|
||||
new: 'नयाँ',
|
||||
open: 'खोल्नुहोस्',
|
||||
actions: {
|
||||
actions: 'कार्यहरू',
|
||||
new: 'नयाँ...',
|
||||
browse: 'ब्राउज गर्नुहोस्...',
|
||||
save: 'सुरक्षित गर्नुहोस्',
|
||||
import: 'डाटाबेस आयात गर्नुहोस्',
|
||||
export_sql: 'SQL निर्यात गर्नुहोस्',
|
||||
export_as: 'निर्यात गर्नुहोस्',
|
||||
delete_diagram: 'डायाग्राम हटाउनुहोस्',
|
||||
exit: 'बाहिर निस्कनुहोस्',
|
||||
delete_diagram: 'हटाउनुहोस्',
|
||||
},
|
||||
edit: {
|
||||
edit: 'सम्पादन',
|
||||
@@ -26,7 +34,10 @@ export const ne: LanguageTranslation = {
|
||||
hide_sidebar: 'साइडबार लुकाउनुहोस्',
|
||||
hide_cardinality: 'कार्डिन्यालिटी लुकाउनुहोस्',
|
||||
show_cardinality: 'कार्डिन्यालिटी देखाउनुहोस्',
|
||||
hide_field_attributes: 'फिल्ड विशेषताहरू लुकाउनुहोस्',
|
||||
show_field_attributes: 'फिल्ड विशेषताहरू देखाउनुहोस्',
|
||||
zoom_on_scroll: 'स्क्रोलमा जुम गर्नुहोस्',
|
||||
show_views: 'डाटाबेस भ्यूहरू',
|
||||
theme: 'थिम',
|
||||
show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्',
|
||||
hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्',
|
||||
@@ -64,22 +75,13 @@ export const ne: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'डायाग्राम पुनः क्रमबद्ध गर्नुहोस्',
|
||||
title: 'डायाग्राम स्वचालित मिलाउनुहोस्',
|
||||
description:
|
||||
'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।',
|
||||
reorder: 'पुनः क्रमबद्ध गर्नुहोस्',
|
||||
reorder: 'स्वचालित मिलाउनुहोस्',
|
||||
cancel: 'रद्द गर्नुहोस्',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'विविध स्कीमहरू',
|
||||
description:
|
||||
'{{schemasCount}} डायाग्राममा स्कीमहरू। हालको रूपमा देखाइएको छ: {{formattedSchemas}}।',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'कुनै पनि छैन',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'प्रतिलिपि असफल',
|
||||
@@ -114,14 +116,11 @@ export const ne: LanguageTranslation = {
|
||||
copied: 'प्रतिलिपि गरियो!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'स्कीम:',
|
||||
filter_by_schema: 'स्कीम अनुसार फिल्टर गर्नुहोस्',
|
||||
search_schema: 'स्कीम खोज्नुहोस्...',
|
||||
no_schemas_found: 'कुनै स्कीमहरू फेला परेनन्',
|
||||
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
|
||||
tables_section: {
|
||||
tables: 'तालिकाहरू',
|
||||
add_table: 'तालिका थप्नुहोस्',
|
||||
add_view: 'भ्यू थप्नुहोस्',
|
||||
filter: 'फिल्टर',
|
||||
collapse: 'सबै लुकाउनुहोस्',
|
||||
// TODO: Translate
|
||||
@@ -147,6 +146,7 @@ export const ne: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'क्षेत्र विशेषताहरू',
|
||||
unique: 'अनन्य',
|
||||
auto_increment: 'स्वचालित वृद्धि',
|
||||
comments: 'टिप्पणीहरू',
|
||||
no_comments: 'कुनै टिप्पणीहरू छैनन्',
|
||||
delete_field: 'क्षेत्र हटाउनुहोस्',
|
||||
@@ -155,11 +155,14 @@ export const ne: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'परिशुद्धता',
|
||||
scale: 'स्केल',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'सूचक विशेषताहरू',
|
||||
name: 'नाम',
|
||||
unique: 'अनन्य',
|
||||
index_type: 'इन्डेक्स प्रकार',
|
||||
delete_index: 'सूचक हटाउनुहोस्',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -176,12 +179,15 @@ export const ne: LanguageTranslation = {
|
||||
description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'सम्बन्धहरू',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'फिल्टर',
|
||||
add_relationship: 'सम्बन्ध थप्नुहोस्',
|
||||
collapse: 'सबै लुकाउनुहोस्',
|
||||
add_relationship: 'सम्बन्ध थप्नुहोस्',
|
||||
relationships: 'सम्बन्धहरू',
|
||||
dependencies: 'डिपेन्डेन्सीहरू',
|
||||
relationship: {
|
||||
relationship: 'सम्बन्ध',
|
||||
primary: 'मुख्य तालिका',
|
||||
foreign: 'परिचित तालिका',
|
||||
cardinality: 'कार्डिन्यालिटी',
|
||||
@@ -191,16 +197,8 @@ export const ne: LanguageTranslation = {
|
||||
delete_relationship: 'हटाउनुहोस्',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'कुनै सम्बन्धहरू छैनन्',
|
||||
description: 'तालिकाहरू जोड्नका लागि एक सम्बन्ध बनाउनुहोस्',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'डिपेन्डेन्सीहरू',
|
||||
filter: 'फिल्टर',
|
||||
collapse: 'सबै लुकाउनुहोस्',
|
||||
dependency: {
|
||||
dependency: 'डिपेन्डेन्सी',
|
||||
table: 'तालिका',
|
||||
dependent_table: 'विचलित तालिका',
|
||||
delete_dependency: 'हटाउनुहोस्',
|
||||
@@ -210,9 +208,8 @@ export const ne: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'कुनै डिपेन्डेन्सीहरू छैनन्',
|
||||
description:
|
||||
'डिपेन्डेन्सीहरू देखाउनका लागि एक व्यू बनाउनुहोस्',
|
||||
title: 'कुनै सम्बन्धहरू छैनन्',
|
||||
description: 'सुरु गर्नका लागि एक सम्बन्ध बनाउनुहोस्',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -252,12 +249,16 @@ export const ne: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'कुनै enum मानहरू परिभाषित छैनन्',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -271,7 +272,11 @@ export const ne: LanguageTranslation = {
|
||||
show_all: 'सबै देखाउनुहोस्',
|
||||
undo: 'पूर्ववत',
|
||||
redo: 'पुनः गर्नुहोस्',
|
||||
reorder_diagram: 'पुनः क्रमबद्ध गर्नुहोस्',
|
||||
reorder_diagram: 'डायाग्राम स्वचालित मिलाउनुहोस्',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables:
|
||||
'अतिरिक्त तालिकाहरू हाइलाइट गर्नुहोस्',
|
||||
// TODO: Translate
|
||||
@@ -312,7 +317,7 @@ export const ne: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'डायाग्राम खोल्नुहोस्',
|
||||
title: 'डाटाबेस खोल्नुहोस्',
|
||||
description:
|
||||
'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।',
|
||||
table_columns: {
|
||||
@@ -323,6 +328,12 @@ export const ne: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'रद्द गर्नुहोस्',
|
||||
open: 'खोल्नुहोस्',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'खोल्नुहोस्',
|
||||
duplicate: 'डुप्लिकेट',
|
||||
delete: 'मेटाउनुहोस्',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -409,6 +420,14 @@ export const ne: LanguageTranslation = {
|
||||
confirm: 'परिवर्तन गर्नुहोस्',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'नयाँ स्कीम सिर्जना गर्नुहोस्',
|
||||
description:
|
||||
'अहिलेसम्म कुनै स्कीम अस्तित्वमा छैन। आफ्ना तालिकाहरू व्यवस्थित गर्न आफ्नो पहिलो स्कीम सिर्जना गर्नुहोस्।',
|
||||
create: 'सिर्जना गर्नुहोस्',
|
||||
cancel: 'रद्द गर्नुहोस्',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'हामीलाई अझ राम्रो हुन मदत गर्नुहोस!',
|
||||
description:
|
||||
@@ -464,6 +483,7 @@ export const ne: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'नयाँ तालिका',
|
||||
new_view: 'नयाँ भ्यू',
|
||||
new_relationship: 'नयाँ सम्बन्ध',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -485,6 +505,9 @@ export const ne: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'भाषा परिवर्तन गर्नुहोस्',
|
||||
},
|
||||
|
||||
on: 'सक्रिय',
|
||||
off: 'निष्क्रिय',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const pt_BR: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Novo',
|
||||
browse: 'Navegar',
|
||||
tables: 'Tabelas',
|
||||
refs: 'Refs',
|
||||
areas: 'Áreas',
|
||||
dependencies: 'Dependências',
|
||||
custom_types: 'Tipos Personalizados',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Arquivo',
|
||||
new: 'Novo',
|
||||
open: 'Abrir',
|
||||
actions: {
|
||||
actions: 'Ações',
|
||||
new: 'Novo...',
|
||||
browse: 'Navegar...',
|
||||
save: 'Salvar',
|
||||
import: 'Importar Banco de Dados',
|
||||
export_sql: 'Exportar SQL',
|
||||
export_as: 'Exportar como',
|
||||
delete_diagram: 'Excluir Diagrama',
|
||||
exit: 'Sair',
|
||||
delete_diagram: 'Excluir',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Editar',
|
||||
@@ -26,7 +34,10 @@ export const pt_BR: LanguageTranslation = {
|
||||
hide_sidebar: 'Ocultar Barra Lateral',
|
||||
hide_cardinality: 'Ocultar Cardinalidade',
|
||||
show_cardinality: 'Mostrar Cardinalidade',
|
||||
hide_field_attributes: 'Ocultar Atributos de Campo',
|
||||
show_field_attributes: 'Mostrar Atributos de Campo',
|
||||
zoom_on_scroll: 'Zoom ao Rolar',
|
||||
show_views: 'Visualizações do Banco de Dados',
|
||||
theme: 'Tema',
|
||||
show_dependencies: 'Mostrar Dependências',
|
||||
hide_dependencies: 'Ocultar Dependências',
|
||||
@@ -64,22 +75,13 @@ export const pt_BR: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Reordenar Diagrama',
|
||||
title: 'Organizar Diagrama Automaticamente',
|
||||
description:
|
||||
'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?',
|
||||
reorder: 'Reordenar',
|
||||
reorder: 'Organizar Automaticamente',
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Falha na cópia',
|
||||
@@ -114,14 +116,11 @@ export const pt_BR: LanguageTranslation = {
|
||||
copied: 'Copiado!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tabelas',
|
||||
add_table: 'Adicionar Tabela',
|
||||
add_view: 'Adicionar Visualização',
|
||||
filter: 'Filtrar',
|
||||
collapse: 'Colapsar Todas',
|
||||
// TODO: Translate
|
||||
@@ -147,6 +146,7 @@ export const pt_BR: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Atributos do Campo',
|
||||
unique: 'Único',
|
||||
auto_increment: 'Incremento Automático',
|
||||
comments: 'Comentários',
|
||||
no_comments: 'Sem comentários',
|
||||
delete_field: 'Excluir Campo',
|
||||
@@ -155,11 +155,14 @@ export const pt_BR: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Precisão',
|
||||
scale: 'Escala',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Atributos do Índice',
|
||||
name: 'Nome',
|
||||
unique: 'Único',
|
||||
index_type: 'Tipo de Índice',
|
||||
delete_index: 'Excluir Índice',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -176,12 +179,15 @@ export const pt_BR: LanguageTranslation = {
|
||||
description: 'Crie uma tabela para começar',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Relacionamentos',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Filtrar',
|
||||
add_relationship: 'Adicionar Relacionamento',
|
||||
collapse: 'Colapsar Todas',
|
||||
add_relationship: 'Adicionar Relacionamento',
|
||||
relationships: 'Relacionamentos',
|
||||
dependencies: 'Dependências',
|
||||
relationship: {
|
||||
relationship: 'Relacionamento',
|
||||
primary: 'Tabela Primária',
|
||||
foreign: 'Tabela Referenciada',
|
||||
cardinality: 'Cardinalidade',
|
||||
@@ -191,16 +197,8 @@ export const pt_BR: LanguageTranslation = {
|
||||
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: 'Dependência',
|
||||
table: 'Tabela',
|
||||
dependent_table: 'Visualização Dependente',
|
||||
delete_dependency: 'Excluir',
|
||||
@@ -210,8 +208,8 @@ export const pt_BR: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Sem dependências',
|
||||
description: 'Crie uma visualização para começar',
|
||||
title: 'Sem relacionamentos',
|
||||
description: 'Crie um relacionamento para começar',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -251,12 +249,16 @@ export const pt_BR: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'Nenhum valor de enum definido',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -270,7 +272,11 @@ export const pt_BR: LanguageTranslation = {
|
||||
show_all: 'Mostrar Tudo',
|
||||
undo: 'Desfazer',
|
||||
redo: 'Refazer',
|
||||
reorder_diagram: 'Reordenar Diagrama',
|
||||
reorder_diagram: 'Organizar Diagrama Automaticamente',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Destacar Tabelas Sobrepostas',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -311,7 +317,7 @@ export const pt_BR: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Abrir Diagrama',
|
||||
title: 'Abrir Banco de Dados',
|
||||
description: 'Selecione um diagrama para abrir da lista abaixo.',
|
||||
table_columns: {
|
||||
name: 'Nome',
|
||||
@@ -321,6 +327,12 @@ export const pt_BR: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Cancelar',
|
||||
open: 'Abrir',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Abrir',
|
||||
duplicate: 'Duplicar',
|
||||
delete: 'Excluir',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -407,6 +419,14 @@ export const pt_BR: LanguageTranslation = {
|
||||
confirm: 'Alterar',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Criar Novo Esquema',
|
||||
description:
|
||||
'Ainda não existem esquemas. Crie seu primeiro esquema para organizar suas tabelas.',
|
||||
create: 'Criar',
|
||||
cancel: 'Cancelar',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Ajude-nos a melhorar!',
|
||||
description:
|
||||
@@ -462,6 +482,7 @@ export const pt_BR: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Nova Tabela',
|
||||
new_view: 'Nova Visualização',
|
||||
new_relationship: 'Novo Relacionamento',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -484,6 +505,9 @@ export const pt_BR: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Idioma',
|
||||
},
|
||||
|
||||
on: 'Ligado',
|
||||
off: 'Desligado',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ru: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Новая',
|
||||
browse: 'Обзор',
|
||||
tables: 'Таблицы',
|
||||
refs: 'Ссылки',
|
||||
areas: 'Области',
|
||||
dependencies: 'Зависимости',
|
||||
custom_types: 'Пользовательские типы',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Файл',
|
||||
new: 'Создать',
|
||||
open: 'Открыть',
|
||||
actions: {
|
||||
actions: 'Действия',
|
||||
new: 'Новая...',
|
||||
browse: 'Обзор...',
|
||||
save: 'Сохранить',
|
||||
import: 'Импортировать базу данных',
|
||||
export_sql: 'Экспорт SQL',
|
||||
export_as: 'Экспортировать как',
|
||||
delete_diagram: 'Удалить диаграмму',
|
||||
exit: 'Выход',
|
||||
delete_diagram: 'Удалить',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Изменение',
|
||||
@@ -26,7 +34,10 @@ export const ru: LanguageTranslation = {
|
||||
hide_sidebar: 'Скрыть боковую панель',
|
||||
hide_cardinality: 'Скрыть виды связи',
|
||||
show_cardinality: 'Показать виды связи',
|
||||
show_field_attributes: 'Показать атрибуты поля',
|
||||
hide_field_attributes: 'Скрыть атрибуты поля',
|
||||
zoom_on_scroll: 'Увеличение при прокрутке',
|
||||
show_views: 'Представления базы данных',
|
||||
theme: 'Тема',
|
||||
show_dependencies: 'Показать зависимости',
|
||||
hide_dependencies: 'Скрыть зависимости',
|
||||
@@ -62,22 +73,13 @@ export const ru: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Переупорядочить диаграмму',
|
||||
title: 'Автоматическая расстановка диаграммы',
|
||||
description:
|
||||
'Это действие переставит все таблицы на диаграмме. Хотите продолжить?',
|
||||
reorder: 'Изменить порядок',
|
||||
reorder: 'Автоматическая расстановка',
|
||||
cancel: 'Отменить',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'Множественные схемы',
|
||||
description:
|
||||
'{{schemasCount}} схем в этой диаграмме. В данный момент отображается: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'никто',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Ошибка копирования',
|
||||
@@ -111,14 +113,11 @@ export const ru: LanguageTranslation = {
|
||||
show_less: 'Показать меньше',
|
||||
|
||||
side_panel: {
|
||||
schema: 'Схема:',
|
||||
filter_by_schema: 'Фильтр по схеме',
|
||||
search_schema: 'Схема поиска...',
|
||||
no_schemas_found: 'Схемы не найдены.',
|
||||
view_all_options: 'Просмотреть все варианты...',
|
||||
tables_section: {
|
||||
tables: 'Таблицы',
|
||||
add_table: 'Добавить таблицу',
|
||||
add_view: 'Добавить представление',
|
||||
filter: 'Фильтр',
|
||||
collapse: 'Свернуть все',
|
||||
clear: 'Очистить фильтр',
|
||||
@@ -144,6 +143,7 @@ export const ru: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Атрибуты поля',
|
||||
unique: 'Уникальный',
|
||||
auto_increment: 'Автоинкремент',
|
||||
comments: 'Комментарии',
|
||||
no_comments: 'Нет комментария',
|
||||
delete_field: 'Удалить поле',
|
||||
@@ -151,11 +151,14 @@ export const ru: LanguageTranslation = {
|
||||
default_value: 'Default Value',
|
||||
no_default: 'No default',
|
||||
character_length: 'Макс. длина',
|
||||
precision: 'Точность',
|
||||
scale: 'Масштаб',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Атрибуты индекса',
|
||||
name: 'Имя',
|
||||
unique: 'Уникальный',
|
||||
index_type: 'Тип индекса',
|
||||
delete_index: 'Удалить индекс',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -172,12 +175,15 @@ export const ru: LanguageTranslation = {
|
||||
description: 'Создайте таблицу, чтобы начать',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Отношения',
|
||||
refs_section: {
|
||||
refs: 'Ссылки',
|
||||
filter: 'Фильтр',
|
||||
add_relationship: 'Добавить отношение',
|
||||
collapse: 'Свернуть все',
|
||||
add_relationship: 'Добавить отношение',
|
||||
relationships: 'Отношения',
|
||||
dependencies: 'Зависимости',
|
||||
relationship: {
|
||||
relationship: 'Отношение',
|
||||
primary: 'Основная таблица',
|
||||
foreign: 'Справочная таблица',
|
||||
cardinality: 'Тип множественной связи',
|
||||
@@ -187,18 +193,10 @@ export const ru: LanguageTranslation = {
|
||||
delete_relationship: 'Удалить',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Нет отношений',
|
||||
description: 'Создайте связь для соединения таблиц',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'Зависимости',
|
||||
filter: 'Фильтр',
|
||||
collapse: 'Свернуть все',
|
||||
dependency: {
|
||||
table: 'Стол',
|
||||
dependent_table: 'Зависимый вид',
|
||||
dependency: 'Зависимость',
|
||||
table: 'Таблица',
|
||||
dependent_table: 'Зависимое представление',
|
||||
delete_dependency: 'Удалить',
|
||||
dependency_actions: {
|
||||
title: 'Действия',
|
||||
@@ -206,8 +204,8 @@ export const ru: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Нет зависимостей',
|
||||
description: 'Создайте представление, чтобы начать',
|
||||
title: 'Нет отношений',
|
||||
description: 'Создайте отношение, чтобы начать',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -248,12 +246,16 @@ export const ru: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'Значения перечисления не определены',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -267,7 +269,11 @@ export const ru: LanguageTranslation = {
|
||||
show_all: 'Показать все',
|
||||
undo: 'Отменить',
|
||||
redo: 'Вернуть',
|
||||
reorder_diagram: 'Переупорядочить диаграмму',
|
||||
reorder_diagram: 'Автоматическая расстановка диаграммы',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Выделение перекрывающихся таблиц',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -307,7 +313,7 @@ export const ru: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Открыть диаграмму',
|
||||
title: 'Открыть базу данных',
|
||||
description:
|
||||
'Выберите диаграмму, которую нужно открыть, из списка ниже.',
|
||||
table_columns: {
|
||||
@@ -318,6 +324,12 @@ export const ru: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Отмена',
|
||||
open: 'Открыть',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Открыть',
|
||||
duplicate: 'Дублировать',
|
||||
delete: 'Удалить',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -404,6 +416,14 @@ export const ru: LanguageTranslation = {
|
||||
confirm: 'Изменить',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Создать новую схему',
|
||||
description:
|
||||
'Схемы еще не существуют. Создайте вашу первую схему, чтобы организовать таблицы.',
|
||||
create: 'Создать',
|
||||
cancel: 'Отменить',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Помогите нам стать лучше!',
|
||||
description:
|
||||
@@ -458,6 +478,7 @@ export const ru: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Создать таблицу',
|
||||
new_view: 'Новое представление',
|
||||
new_relationship: 'Создать отношение',
|
||||
new_area: 'Новая область',
|
||||
},
|
||||
@@ -479,6 +500,9 @@ export const ru: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Сменить язык',
|
||||
},
|
||||
|
||||
on: 'Вкл',
|
||||
off: 'Выкл',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const te: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'కొత్తది',
|
||||
browse: 'బ్రాఉజ్',
|
||||
tables: 'టేబల్లు',
|
||||
refs: 'సంబంధాలు',
|
||||
areas: 'ప్రదేశాలు',
|
||||
dependencies: 'ఆధారతలు',
|
||||
custom_types: 'కస్టమ్ టైప్స్',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ఫైల్',
|
||||
new: 'కొత్తది',
|
||||
open: 'తెరవు',
|
||||
actions: {
|
||||
actions: 'చర్యలు',
|
||||
new: 'కొత్తది...',
|
||||
browse: 'బ్రాఉజ్ చేయండి...',
|
||||
save: 'సేవ్',
|
||||
import: 'డేటాబేస్ను దిగుమతి చేసుకోండి',
|
||||
export_sql: 'SQL ఎగుమతి',
|
||||
export_as: 'వగా ఎగుమతి చేయండి',
|
||||
delete_diagram: 'చిత్రాన్ని తొలగించండి',
|
||||
exit: 'నిష్క్రమించు',
|
||||
delete_diagram: 'తొలగించండి',
|
||||
},
|
||||
edit: {
|
||||
edit: 'సవరించు',
|
||||
@@ -26,7 +34,10 @@ export const te: LanguageTranslation = {
|
||||
hide_sidebar: 'సైడ్బార్ దాచండి',
|
||||
hide_cardinality: 'కార్డినాలిటీని దాచండి',
|
||||
show_cardinality: 'కార్డినాలిటీని చూపించండి',
|
||||
show_field_attributes: 'ఫీల్డ్ గుణాలను చూపించు',
|
||||
hide_field_attributes: 'ఫీల్డ్ గుణాలను దాచండి',
|
||||
zoom_on_scroll: 'స్క్రోల్పై జూమ్',
|
||||
show_views: 'డేటాబేస్ వ్యూలు',
|
||||
theme: 'థీమ్',
|
||||
show_dependencies: 'ఆధారాలు చూపించండి',
|
||||
hide_dependencies: 'ఆధారాలను దాచండి',
|
||||
@@ -64,22 +75,13 @@ export const te: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'చిత్రాన్ని పునఃసరిచేయండి',
|
||||
title: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
|
||||
description:
|
||||
'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?',
|
||||
reorder: 'పునఃసరిచేయండి',
|
||||
reorder: 'స్వయంచాలకంగా అమర్చండి',
|
||||
cancel: 'రద్దు',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'బహుళ స్కీమాలు',
|
||||
description:
|
||||
'{{schemasCount}} స్కీమాలు ఈ చిత్రంలో ఉన్నాయి. ప్రస్తుత స్కీమాలు: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'ఎదరికాదు',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'కాపీ విఫలమైంది',
|
||||
@@ -114,14 +116,11 @@ export const te: LanguageTranslation = {
|
||||
copied: 'కాపీ చేయబడింది!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'స్కీమా:',
|
||||
filter_by_schema: 'స్కీమా ద్వారా ఫిల్టర్ చేయండి',
|
||||
search_schema: 'స్కీమా కోసం శోధించండి...',
|
||||
no_schemas_found: 'ఏ స్కీమాలు కూడా కనుగొనబడలేదు.',
|
||||
view_all_options: 'అన్ని ఎంపికలను చూడండి...',
|
||||
tables_section: {
|
||||
tables: 'పట్టికలు',
|
||||
add_table: 'పట్టికను జోడించు',
|
||||
add_view: 'వ్యూ జోడించండి',
|
||||
filter: 'ఫిల్టర్',
|
||||
collapse: 'అన్ని కూల్ చేయి',
|
||||
// TODO: Translate
|
||||
@@ -147,6 +146,7 @@ export const te: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'ఫీల్డ్ గుణాలు',
|
||||
unique: 'అద్వితీయ',
|
||||
auto_increment: 'ఆటో ఇంక్రిమెంట్',
|
||||
comments: 'వ్యాఖ్యలు',
|
||||
no_comments: 'వ్యాఖ్యలు లేవు',
|
||||
delete_field: 'ఫీల్డ్ తొలగించు',
|
||||
@@ -155,11 +155,14 @@ export const te: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'సూక్ష్మత',
|
||||
scale: 'స్కేల్',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'ఇండెక్స్ గుణాలు',
|
||||
name: 'పేరు',
|
||||
unique: 'అద్వితీయ',
|
||||
index_type: 'ఇండెక్స్ రకం',
|
||||
delete_index: 'ఇండెక్స్ తొలగించు',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -177,12 +180,15 @@ export const te: LanguageTranslation = {
|
||||
description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'సంబంధాలు',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'ఫిల్టర్',
|
||||
add_relationship: 'సంబంధం జోడించు',
|
||||
collapse: 'అన్ని కూల్ చేయి',
|
||||
add_relationship: 'సంబంధం జోడించు',
|
||||
relationships: 'సంబంధాలు',
|
||||
dependencies: 'ఆధారాలు',
|
||||
relationship: {
|
||||
relationship: 'సంబంధం',
|
||||
primary: 'ప్రాథమిక పట్టిక',
|
||||
foreign: 'సూచించబడిన పట్టిక',
|
||||
cardinality: 'కార్డినాలిటీ',
|
||||
@@ -192,16 +198,8 @@ export const te: LanguageTranslation = {
|
||||
delete_relationship: 'సంబంధం తొలగించు',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'సంబంధాలు లేవు',
|
||||
description: 'పట్టికలను అనుసంధించడానికి సంబంధం సృష్టించండి',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'ఆధారాలు',
|
||||
filter: 'ఫిల్టర్',
|
||||
collapse: 'అన్ని కూల్ చేయి',
|
||||
dependency: {
|
||||
dependency: 'ఆధారం',
|
||||
table: 'పట్టిక',
|
||||
dependent_table: 'ఆధారిత వీక్షణ',
|
||||
delete_dependency: 'ఆధారాన్ని తొలగించు',
|
||||
@@ -211,8 +209,8 @@ export const te: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'ఆధారాలు లేవు',
|
||||
description: 'ప్రారంభించడానికి ఒక వీక్షణ సృష్టించండి',
|
||||
title: 'సంబంధాలు లేవు',
|
||||
description: 'ప్రారంభించడానికి ఒక సంబంధం సృష్టించండి',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -252,12 +250,16 @@ export const te: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'ఏ enum విలువలు నిర్వచించబడలేదు',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -271,7 +273,11 @@ export const te: LanguageTranslation = {
|
||||
show_all: 'అన్ని చూపించు',
|
||||
undo: 'తిరిగి చేయు',
|
||||
redo: 'మరలా చేయు',
|
||||
reorder_diagram: 'చిత్రాన్ని పునఃసరిచేయండి',
|
||||
reorder_diagram: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'అవకాశించు పట్టికలను హైలైట్ చేయండి',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -312,7 +318,7 @@ export const te: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'చిత్రం తెరవండి',
|
||||
title: 'డేటాబేస్ తెరవండి',
|
||||
description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.',
|
||||
table_columns: {
|
||||
name: 'పేరు',
|
||||
@@ -322,6 +328,12 @@ export const te: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'రద్దు',
|
||||
open: 'తెరవు',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'తెరవు',
|
||||
duplicate: 'నకలు',
|
||||
delete: 'తొలగించు',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -408,6 +420,14 @@ export const te: LanguageTranslation = {
|
||||
confirm: 'మార్చు',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'కొత్త స్కీమా సృష్టించండి',
|
||||
description:
|
||||
'ఇంకా ఏ స్కీమాలు లేవు. మీ పట్టికలను వ్యవస్థీకరించడానికి మీ మొదటి స్కీమాను సృష్టించండి.',
|
||||
create: 'సృష్టించు',
|
||||
cancel: 'రద్దు',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'మా సహాయంతో మెరుగుపరచండి!',
|
||||
description:
|
||||
@@ -466,6 +486,7 @@ export const te: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'కొత్త పట్టిక',
|
||||
new_view: 'కొత్త వ్యూ',
|
||||
new_relationship: 'కొత్త సంబంధం',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -489,6 +510,9 @@ export const te: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'భాష మార్చు',
|
||||
},
|
||||
|
||||
on: 'ఆన్',
|
||||
off: 'ఆఫ్',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const tr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Yeni',
|
||||
browse: 'Gözat',
|
||||
tables: 'Tablolar',
|
||||
refs: 'Refs',
|
||||
areas: 'Alanlar',
|
||||
dependencies: 'Bağımlılıklar',
|
||||
custom_types: 'Özel Tipler',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Dosya',
|
||||
new: 'Yeni',
|
||||
open: 'Aç',
|
||||
actions: {
|
||||
actions: 'Eylemler',
|
||||
new: 'Yeni...',
|
||||
browse: 'Gözat...',
|
||||
save: 'Kaydet',
|
||||
import: 'Veritabanı İçe Aktar',
|
||||
export_sql: 'SQL Olarak Dışa Aktar',
|
||||
export_as: 'Olarak Dışa Aktar',
|
||||
delete_diagram: 'Diyagramı Sil',
|
||||
exit: 'Çıkış',
|
||||
delete_diagram: 'Sil',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Düzenle',
|
||||
@@ -26,7 +34,10 @@ export const tr: LanguageTranslation = {
|
||||
hide_sidebar: 'Kenar Çubuğunu Gizle',
|
||||
hide_cardinality: 'Kardinaliteyi Gizle',
|
||||
show_cardinality: 'Kardinaliteyi Göster',
|
||||
show_field_attributes: 'Alan Özelliklerini Göster',
|
||||
hide_field_attributes: 'Alan Özelliklerini Gizle',
|
||||
zoom_on_scroll: 'Kaydırarak Yakınlaştır',
|
||||
show_views: 'Veritabanı Görünümleri',
|
||||
theme: 'Tema',
|
||||
show_dependencies: 'Bağımlılıkları Göster',
|
||||
hide_dependencies: 'Bağımlılıkları Gizle',
|
||||
@@ -64,22 +75,13 @@ export const tr: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Diyagramı Yeniden Sırala',
|
||||
title: 'Diyagramı Otomatik Düzenle',
|
||||
description:
|
||||
'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?',
|
||||
reorder: 'Yeniden Sırala',
|
||||
reorder: 'Otomatik Düzenle',
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Kopyalama başarısız',
|
||||
@@ -113,14 +115,11 @@ export const tr: LanguageTranslation = {
|
||||
copy_to_clipboard: 'Panoya Kopyala',
|
||||
copied: 'Kopyalandı!',
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Tablolar',
|
||||
add_table: 'Tablo Ekle',
|
||||
add_view: 'Görünüm Ekle',
|
||||
filter: 'Filtrele',
|
||||
collapse: 'Hepsini Daralt',
|
||||
// TODO: Translate
|
||||
@@ -146,6 +145,7 @@ export const tr: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Alan Özellikleri',
|
||||
unique: 'Tekil',
|
||||
auto_increment: 'Otomatik Artış',
|
||||
comments: 'Yorumlar',
|
||||
no_comments: 'Yorum yok',
|
||||
delete_field: 'Alanı Sil',
|
||||
@@ -154,11 +154,14 @@ export const tr: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Hassasiyet',
|
||||
scale: 'Ölçek',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'İndeks Özellikleri',
|
||||
name: 'Ad',
|
||||
unique: 'Tekil',
|
||||
index_type: 'İndeks Türü',
|
||||
delete_index: 'İndeksi Sil',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -176,12 +179,15 @@ export const tr: LanguageTranslation = {
|
||||
description: 'Başlamak için bir tablo oluşturun',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'İlişkiler',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Filtrele',
|
||||
add_relationship: 'İlişki Ekle',
|
||||
collapse: 'Hepsini Daralt',
|
||||
add_relationship: 'İlişki Ekle',
|
||||
relationships: 'İlişkiler',
|
||||
dependencies: 'Bağımlılıklar',
|
||||
relationship: {
|
||||
relationship: 'İlişki',
|
||||
primary: 'Birincil Tablo',
|
||||
foreign: 'Referans Tablo',
|
||||
cardinality: 'Kardinalite',
|
||||
@@ -191,16 +197,8 @@ export const tr: LanguageTranslation = {
|
||||
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: 'Bağımlılık',
|
||||
table: 'Tablo',
|
||||
dependent_table: 'Bağımlı Görünüm',
|
||||
delete_dependency: 'Sil',
|
||||
@@ -210,8 +208,8 @@ export const tr: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Bağımlılık yok',
|
||||
description: 'Başlamak için bir görünüm oluşturun',
|
||||
title: 'İlişki yok',
|
||||
description: 'Başlamak için bir ilişki oluşturun',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -251,12 +249,16 @@ export const tr: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'Tanımlanmış enum değeri yok',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -269,7 +271,11 @@ export const tr: LanguageTranslation = {
|
||||
show_all: 'Hepsini Gör',
|
||||
undo: 'Geri Al',
|
||||
redo: 'Yinele',
|
||||
reorder_diagram: 'Diyagramı Yeniden Sırala',
|
||||
reorder_diagram: 'Diyagramı Otomatik Düzenle',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Çakışan Tabloları Vurgula',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -307,7 +313,7 @@ export const tr: LanguageTranslation = {
|
||||
import: 'İçe Aktar',
|
||||
},
|
||||
open_diagram_dialog: {
|
||||
title: 'Diyagramı Aç',
|
||||
title: 'Veritabanı Aç',
|
||||
description: 'Aşağıdaki listeden açmak için bir diyagram seçin.',
|
||||
table_columns: {
|
||||
name: 'Ad',
|
||||
@@ -317,6 +323,12 @@ export const tr: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'İptal',
|
||||
open: 'Aç',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Aç',
|
||||
duplicate: 'Kopyala',
|
||||
delete: 'Sil',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -397,6 +409,14 @@ export const tr: LanguageTranslation = {
|
||||
cancel: 'İptal',
|
||||
confirm: 'Değiştir',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Yeni Şema Oluştur',
|
||||
description:
|
||||
'Henüz hiç şema mevcut değil. Tablolarınızı düzenlemek için ilk şemanızı oluşturun.',
|
||||
create: 'Oluştur',
|
||||
cancel: 'İptal',
|
||||
},
|
||||
star_us_dialog: {
|
||||
title: 'Bize yardım et!',
|
||||
description:
|
||||
@@ -451,6 +471,7 @@ export const tr: LanguageTranslation = {
|
||||
},
|
||||
canvas_context_menu: {
|
||||
new_table: 'Yeni Tablo',
|
||||
new_view: 'Yeni Görünüm',
|
||||
new_relationship: 'Yeni İlişki',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -473,6 +494,9 @@ export const tr: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Dil',
|
||||
},
|
||||
|
||||
on: 'Açık',
|
||||
off: 'Kapalı',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const uk: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Нова',
|
||||
browse: 'Огляд',
|
||||
tables: 'Таблиці',
|
||||
refs: 'Зв’язки',
|
||||
areas: 'Області',
|
||||
dependencies: 'Залежності',
|
||||
custom_types: 'Користувацькі типи',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Файл',
|
||||
new: 'Новий',
|
||||
open: 'Відкрити',
|
||||
actions: {
|
||||
actions: 'Дії',
|
||||
new: 'Нова...',
|
||||
browse: 'Огляд...',
|
||||
save: 'Зберегти',
|
||||
import: 'Імпорт бази даних',
|
||||
export_sql: 'Експорт SQL',
|
||||
export_as: 'Експортувати як',
|
||||
delete_diagram: 'Видалити діаграму',
|
||||
exit: 'Вийти',
|
||||
delete_diagram: 'Видалити',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Редагувати',
|
||||
@@ -26,7 +34,10 @@ export const uk: LanguageTranslation = {
|
||||
hide_sidebar: 'Приховати бічну панель',
|
||||
hide_cardinality: 'Приховати потужність',
|
||||
show_cardinality: 'Показати кардинальність',
|
||||
show_field_attributes: 'Показати атрибути полів',
|
||||
hide_field_attributes: 'Приховати атрибути полів',
|
||||
zoom_on_scroll: 'Масштабувати прокручуванням',
|
||||
show_views: 'Представлення бази даних',
|
||||
theme: 'Тема',
|
||||
show_dependencies: 'Показати залежності',
|
||||
hide_dependencies: 'Приховати залежності',
|
||||
@@ -62,22 +73,13 @@ export const uk: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Перевпорядкувати діаграму',
|
||||
title: 'Автоматичне розміщення діаграми',
|
||||
description:
|
||||
'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?',
|
||||
reorder: 'Перевпорядкувати',
|
||||
reorder: 'Автоматичне розміщення',
|
||||
cancel: 'Скасувати',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: 'Кілька схем',
|
||||
description:
|
||||
'{{schemasCount}} схеми на цій діаграмі. Зараз відображається: {{formattedSchemas}}.',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: 'немає',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: 'Помилка копіювання',
|
||||
@@ -112,14 +114,11 @@ export const uk: LanguageTranslation = {
|
||||
copied: 'Скопійовано!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'Схема:',
|
||||
filter_by_schema: 'Фільтрувати за схемою',
|
||||
search_schema: 'Пошук схеми…',
|
||||
no_schemas_found: 'Схеми не знайдено.',
|
||||
view_all_options: 'Переглянути всі параметри…',
|
||||
tables_section: {
|
||||
tables: 'Таблиці',
|
||||
add_table: 'Додати таблицю',
|
||||
add_view: 'Додати представлення',
|
||||
filter: 'Фільтр',
|
||||
collapse: 'Згорнути все',
|
||||
// TODO: Translate
|
||||
@@ -145,6 +144,7 @@ export const uk: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Атрибути полів',
|
||||
unique: 'Унікальне',
|
||||
auto_increment: 'Автоінкремент',
|
||||
comments: 'Коментарі',
|
||||
no_comments: 'Немає коментарів',
|
||||
delete_field: 'Видалити поле',
|
||||
@@ -153,11 +153,14 @@ export const uk: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Точність',
|
||||
scale: 'Масштаб',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Атрибути індексу',
|
||||
name: 'Назва індекса',
|
||||
unique: 'Унікальний',
|
||||
index_type: 'Тип індексу',
|
||||
delete_index: 'Видалити індекс',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -174,12 +177,15 @@ export const uk: LanguageTranslation = {
|
||||
description: 'Щоб почати, створіть таблицю',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Звʼязки',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Фільтр',
|
||||
add_relationship: 'Додати звʼязок',
|
||||
collapse: 'Згорнути все',
|
||||
add_relationship: 'Додати звʼязок',
|
||||
relationships: 'Звʼязки',
|
||||
dependencies: 'Залежності',
|
||||
relationship: {
|
||||
relationship: 'Звʼязок',
|
||||
primary: 'Первинна таблиця',
|
||||
foreign: 'Посилання на таблицю',
|
||||
cardinality: 'Звʼязок',
|
||||
@@ -189,16 +195,8 @@ export const uk: LanguageTranslation = {
|
||||
delete_relationship: 'Видалити',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Звʼязків немає',
|
||||
description: 'Створіть звʼязок для зʼєднання таблиць',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: 'Залежності',
|
||||
filter: 'Фільтр',
|
||||
collapse: 'Згорнути все',
|
||||
dependency: {
|
||||
dependency: 'Залежність',
|
||||
table: 'Таблиця',
|
||||
dependent_table: 'Залежне подання',
|
||||
delete_dependency: 'Видалити',
|
||||
@@ -208,8 +206,8 @@ export const uk: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Жодних залежностей',
|
||||
description: 'Створіть подання, щоб почати',
|
||||
title: 'Жодних зв’язків',
|
||||
description: 'Створіть зв’язок, щоб почати',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -249,12 +247,16 @@ export const uk: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'Значення переліку не визначені',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -268,7 +270,11 @@ export const uk: LanguageTranslation = {
|
||||
show_all: 'Показати все',
|
||||
undo: 'Скасувати',
|
||||
redo: 'Повторити',
|
||||
reorder_diagram: 'Перевпорядкувати діаграму',
|
||||
reorder_diagram: 'Автоматичне розміщення діаграми',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Показати таблиці, що перекриваються',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -308,7 +314,7 @@ export const uk: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Відкрити діаграму',
|
||||
title: 'Відкрити базу даних',
|
||||
description:
|
||||
'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.',
|
||||
table_columns: {
|
||||
@@ -319,6 +325,12 @@ export const uk: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Скасувати',
|
||||
open: 'Відкрити',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Відкрити',
|
||||
duplicate: 'Дублювати',
|
||||
delete: 'Видалити',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -405,6 +417,14 @@ export const uk: LanguageTranslation = {
|
||||
confirm: 'Змінити',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Створити нову схему',
|
||||
description:
|
||||
'Поки що не існує жодної схеми. Створіть свою першу схему, щоб організувати ваші таблиці.',
|
||||
create: 'Створити',
|
||||
cancel: 'Скасувати',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Допоможіть нам покращитися!',
|
||||
description: 'Поставне на зірку на GitHub? Це лише один клік!',
|
||||
@@ -457,6 +477,7 @@ export const uk: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Нова таблиця',
|
||||
new_view: 'Нове представлення',
|
||||
new_relationship: 'Новий звʼязок',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -478,6 +499,9 @@ export const uk: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Мова',
|
||||
},
|
||||
|
||||
on: 'Увімк',
|
||||
off: 'Вимк',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const vi: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Mới',
|
||||
browse: 'Duyệt',
|
||||
tables: 'Bảng',
|
||||
refs: 'Refs',
|
||||
areas: 'Khu vực',
|
||||
dependencies: 'Phụ thuộc',
|
||||
custom_types: 'Kiểu tùy chỉnh',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Tệp',
|
||||
new: 'Tạo mới',
|
||||
open: 'Mở',
|
||||
actions: {
|
||||
actions: 'Hành động',
|
||||
new: 'Mới...',
|
||||
browse: 'Duyệt...',
|
||||
save: 'Lưu',
|
||||
import: 'Nhập cơ sở dữ liệu',
|
||||
export_sql: 'Xuất SQL',
|
||||
export_as: 'Xuất thành',
|
||||
delete_diagram: 'Xóa sơ đồ',
|
||||
exit: 'Thoát',
|
||||
delete_diagram: 'Xóa',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Sửa',
|
||||
@@ -26,7 +34,10 @@ export const vi: LanguageTranslation = {
|
||||
hide_sidebar: 'Ẩn thanh bên',
|
||||
hide_cardinality: 'Ẩn số lượng',
|
||||
show_cardinality: 'Hiển thị số lượng',
|
||||
show_field_attributes: 'Hiển thị 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',
|
||||
show_views: 'Chế độ xem Cơ sở dữ liệu',
|
||||
theme: 'Chủ đề',
|
||||
show_dependencies: 'Hiển thị các phụ thuộc',
|
||||
hide_dependencies: 'Ẩn các phụ thuộc',
|
||||
@@ -63,22 +74,13 @@ export const vi: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: 'Sắp xếp lại sơ đồ',
|
||||
title: 'Tự động sắp xếp sơ đồ',
|
||||
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?',
|
||||
reorder: 'Sắp xếp',
|
||||
reorder: 'Tự động sắp xếp',
|
||||
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: {
|
||||
unsupported: {
|
||||
title: 'Sao chép thất bại',
|
||||
@@ -113,14 +115,11 @@ export const vi: LanguageTranslation = {
|
||||
copied: 'Đã sao chép!',
|
||||
|
||||
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...',
|
||||
tables_section: {
|
||||
tables: 'Bảng',
|
||||
add_table: 'Thêm bảng',
|
||||
add_view: 'Thêm Chế độ xem',
|
||||
filter: 'Lọc',
|
||||
collapse: 'Thu gọn tất cả',
|
||||
// TODO: Translate
|
||||
@@ -146,6 +145,7 @@ export const vi: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: 'Thuộc tính trường',
|
||||
unique: 'Giá trị duy nhất',
|
||||
auto_increment: 'Tự động tăng',
|
||||
comments: 'Bình luận',
|
||||
no_comments: 'Không có bình luận',
|
||||
delete_field: 'Xóa trường',
|
||||
@@ -154,11 +154,14 @@ export const vi: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: 'Độ chính xác',
|
||||
scale: 'Tỷ lệ',
|
||||
},
|
||||
index_actions: {
|
||||
title: 'Thuộc tính chỉ mục',
|
||||
name: 'Tên',
|
||||
unique: 'Giá trị duy nhất',
|
||||
index_type: 'Loại chỉ mục',
|
||||
delete_index: 'Xóa chỉ mục',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -175,12 +178,15 @@ export const vi: LanguageTranslation = {
|
||||
description: 'Tạo một bảng để bắt đầu',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Quan hệ',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: 'Lọc',
|
||||
add_relationship: 'Thêm quan hệ',
|
||||
collapse: 'Thu gọn tất cả',
|
||||
add_relationship: 'Thêm quan hệ',
|
||||
relationships: 'Quan hệ',
|
||||
dependencies: 'Phụ thuộc',
|
||||
relationship: {
|
||||
relationship: 'Quan hệ',
|
||||
primary: 'Bảng khóa chính',
|
||||
foreign: 'Bảng khóa ngoại',
|
||||
cardinality: 'Quan hệ',
|
||||
@@ -190,16 +196,8 @@ export const vi: LanguageTranslation = {
|
||||
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: 'Phụ thuộc',
|
||||
table: 'Bảng',
|
||||
dependent_table: 'Bảng xem phụ thuộc',
|
||||
delete_dependency: 'Xóa',
|
||||
@@ -209,8 +207,8 @@ export const vi: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: 'Không có phụ thuộc',
|
||||
description: 'Tạo bảng xem phụ thuộc để bắt đầu',
|
||||
title: 'Không có quan hệ',
|
||||
description: 'Tạo một quan hệ để bắt đầu',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -250,12 +248,16 @@ export const vi: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: 'Không có giá trị enum được định nghĩa',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -269,7 +271,11 @@ export const vi: LanguageTranslation = {
|
||||
show_all: 'Hiển thị tất cả',
|
||||
undo: 'Hoàn tác',
|
||||
redo: 'Làm lại',
|
||||
reorder_diagram: 'Sắp xếp lại sơ đồ',
|
||||
reorder_diagram: 'Tự động sắp xếp sơ đồ',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: 'Làm nổi bật các bảng chồng chéo',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -309,7 +315,7 @@ export const vi: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: 'Mở sơ đồ',
|
||||
title: 'Mở cơ sở dữ liệu',
|
||||
description: 'Chọn sơ đồ để mở từ danh sách bên dưới.',
|
||||
table_columns: {
|
||||
name: 'Tên',
|
||||
@@ -319,6 +325,12 @@ export const vi: LanguageTranslation = {
|
||||
},
|
||||
cancel: 'Hủy',
|
||||
open: 'Mở',
|
||||
|
||||
diagram_actions: {
|
||||
open: 'Mở',
|
||||
duplicate: 'Nhân bản',
|
||||
delete: 'Xóa',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -404,6 +416,14 @@ export const vi: LanguageTranslation = {
|
||||
confirm: 'Xác nhận',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: 'Tạo lược đồ mới',
|
||||
description:
|
||||
'Chưa có lược đồ nào. Tạo lược đồ đầu tiên của bạn để tổ chức các bảng.',
|
||||
create: 'Tạo',
|
||||
cancel: 'Hủy',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: 'Hãy giúp chúng tôi cải thiện!',
|
||||
description:
|
||||
@@ -458,6 +478,7 @@ export const vi: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Tạo bảng mới',
|
||||
new_view: 'Chế độ xem Mới',
|
||||
new_relationship: 'Tạo quan hệ mới',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -479,6 +500,9 @@ export const vi: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: 'Ngôn ngữ',
|
||||
},
|
||||
|
||||
on: 'Bật',
|
||||
off: 'Tắt',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const zh_CN: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '新建',
|
||||
browse: '浏览',
|
||||
tables: '表',
|
||||
refs: '引用',
|
||||
areas: '区域',
|
||||
dependencies: '依赖关系',
|
||||
custom_types: '自定义类型',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: '文件',
|
||||
new: '新建',
|
||||
open: '打开',
|
||||
actions: {
|
||||
actions: '操作',
|
||||
new: '新建...',
|
||||
browse: '浏览...',
|
||||
save: '保存',
|
||||
import: '导入数据库',
|
||||
export_sql: '导出 SQL 语句',
|
||||
export_as: '导出为',
|
||||
delete_diagram: '删除关系图',
|
||||
exit: '退出',
|
||||
delete_diagram: '删除',
|
||||
},
|
||||
edit: {
|
||||
edit: '编辑',
|
||||
@@ -26,7 +34,10 @@ export const zh_CN: LanguageTranslation = {
|
||||
hide_sidebar: '隐藏侧边栏',
|
||||
hide_cardinality: '隐藏基数',
|
||||
show_cardinality: '展示基数',
|
||||
show_field_attributes: '展示字段属性',
|
||||
hide_field_attributes: '隐藏字段属性',
|
||||
zoom_on_scroll: '滚动缩放',
|
||||
show_views: '数据库视图',
|
||||
theme: '主题',
|
||||
show_dependencies: '展示依赖',
|
||||
hide_dependencies: '隐藏依赖',
|
||||
@@ -61,21 +72,12 @@ export const zh_CN: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: '重新排列关系图',
|
||||
title: '自动排列关系图',
|
||||
description: '此操作将重新排列关系图中的所有表。是否要继续?',
|
||||
reorder: '重新排列',
|
||||
reorder: '自动排列',
|
||||
cancel: '取消',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '多个模式',
|
||||
description:
|
||||
'此关系图中有 {{schemasCount}} 个模式,当前显示:{{formattedSchemas}}。',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: '无',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '复制失败',
|
||||
@@ -110,14 +112,11 @@ export const zh_CN: LanguageTranslation = {
|
||||
copied: '复制了!',
|
||||
|
||||
side_panel: {
|
||||
schema: '模式:',
|
||||
filter_by_schema: '按模式筛选',
|
||||
search_schema: '搜索模式...',
|
||||
no_schemas_found: '未找到模式。',
|
||||
view_all_options: '查看所有选项...',
|
||||
tables_section: {
|
||||
tables: '表',
|
||||
add_table: '添加表',
|
||||
add_view: '添加视图',
|
||||
filter: '筛选',
|
||||
collapse: '全部折叠',
|
||||
// TODO: Translate
|
||||
@@ -143,6 +142,7 @@ export const zh_CN: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: '字段属性',
|
||||
unique: '唯一',
|
||||
auto_increment: '自动递增',
|
||||
comments: '注释',
|
||||
no_comments: '空',
|
||||
delete_field: '删除字段',
|
||||
@@ -151,11 +151,14 @@ export const zh_CN: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: '精度',
|
||||
scale: '小数位',
|
||||
},
|
||||
index_actions: {
|
||||
title: '索引属性',
|
||||
name: '名称',
|
||||
unique: '唯一',
|
||||
index_type: '索引类型',
|
||||
delete_index: '删除索引',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -172,12 +175,15 @@ export const zh_CN: LanguageTranslation = {
|
||||
description: '新建表以开始',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: '关系',
|
||||
refs_section: {
|
||||
refs: '引用',
|
||||
filter: '筛选',
|
||||
add_relationship: '添加关系',
|
||||
collapse: '全部折叠',
|
||||
add_relationship: '添加关系',
|
||||
relationships: '关系',
|
||||
dependencies: '依赖关系',
|
||||
relationship: {
|
||||
relationship: '关系',
|
||||
primary: '主表',
|
||||
foreign: '被引用表',
|
||||
cardinality: '基数',
|
||||
@@ -187,16 +193,8 @@ export const zh_CN: LanguageTranslation = {
|
||||
delete_relationship: '删除',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: '无关系',
|
||||
description: '创建关系以连接表',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: '依赖关系',
|
||||
filter: '筛选',
|
||||
collapse: '全部折叠',
|
||||
dependency: {
|
||||
dependency: '依赖',
|
||||
table: '表',
|
||||
dependent_table: '依赖视图',
|
||||
delete_dependency: '删除',
|
||||
@@ -206,8 +204,8 @@ export const zh_CN: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: '无依赖',
|
||||
description: '创建视图以开始',
|
||||
title: '无关系',
|
||||
description: '创建关系以开始',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -247,12 +245,16 @@ export const zh_CN: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: '没有定义枚举值',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -266,7 +268,11 @@ export const zh_CN: LanguageTranslation = {
|
||||
show_all: '展示全部',
|
||||
undo: '撤销',
|
||||
redo: '重做',
|
||||
reorder_diagram: '重新排列关系图',
|
||||
reorder_diagram: '自动排列关系图',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: '突出显示重叠的表',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -306,7 +312,7 @@ export const zh_CN: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: '打开关系图',
|
||||
title: '打开数据库',
|
||||
description: '从下面的列表中选择一个图表打开。',
|
||||
table_columns: {
|
||||
name: '名称',
|
||||
@@ -316,6 +322,12 @@ export const zh_CN: LanguageTranslation = {
|
||||
},
|
||||
cancel: '取消',
|
||||
open: '打开',
|
||||
|
||||
diagram_actions: {
|
||||
open: '打开',
|
||||
duplicate: '复制',
|
||||
delete: '删除',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -400,6 +412,13 @@ export const zh_CN: LanguageTranslation = {
|
||||
confirm: '更改',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: '创建新模式',
|
||||
description: '尚未存在任何模式。创建您的第一个模式来组织您的表。',
|
||||
create: '创建',
|
||||
cancel: '取消',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: '帮助我们改进!',
|
||||
description: '您想在 GitHub 上为我们加注星标吗?只需点击一下即可!',
|
||||
@@ -454,6 +473,7 @@ export const zh_CN: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: '新建表',
|
||||
new_view: '新建视图',
|
||||
new_relationship: '新建关系',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -475,6 +495,9 @@ export const zh_CN: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: '语言',
|
||||
},
|
||||
|
||||
on: '开启',
|
||||
off: '关闭',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const zh_TW: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '新建',
|
||||
browse: '瀏覽',
|
||||
tables: '表格',
|
||||
refs: 'Refs',
|
||||
areas: '區域',
|
||||
dependencies: '相依性',
|
||||
custom_types: '自定義類型',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: '檔案',
|
||||
new: '新增',
|
||||
open: '開啟',
|
||||
actions: {
|
||||
actions: '操作',
|
||||
new: '新增...',
|
||||
browse: '瀏覽...',
|
||||
save: '儲存',
|
||||
import: '匯入資料庫',
|
||||
export_sql: '匯出 SQL',
|
||||
export_as: '匯出為特定格式',
|
||||
delete_diagram: '刪除圖表',
|
||||
exit: '退出',
|
||||
delete_diagram: '刪除',
|
||||
},
|
||||
edit: {
|
||||
edit: '編輯',
|
||||
@@ -26,7 +34,10 @@ export const zh_TW: LanguageTranslation = {
|
||||
hide_sidebar: '隱藏側邊欄',
|
||||
hide_cardinality: '隱藏基數',
|
||||
show_cardinality: '顯示基數',
|
||||
hide_field_attributes: '隱藏欄位屬性',
|
||||
show_field_attributes: '顯示欄位屬性',
|
||||
zoom_on_scroll: '滾動縮放',
|
||||
show_views: '資料庫檢視',
|
||||
theme: '主題',
|
||||
show_dependencies: '顯示相依性',
|
||||
hide_dependencies: '隱藏相依性',
|
||||
@@ -61,21 +72,12 @@ export const zh_TW: LanguageTranslation = {
|
||||
},
|
||||
|
||||
reorder_diagram_alert: {
|
||||
title: '重新排列圖表',
|
||||
title: '自動排列圖表',
|
||||
description: '此操作將重新排列圖表中的所有表格。是否繼續?',
|
||||
reorder: '重新排列',
|
||||
reorder: '自動排列',
|
||||
cancel: '取消',
|
||||
},
|
||||
|
||||
multiple_schemas_alert: {
|
||||
title: '多重 Schema',
|
||||
description:
|
||||
'此圖表中包含 {{schemasCount}} 個 Schema,目前顯示:{{formattedSchemas}}。',
|
||||
// TODO: Translate
|
||||
show_me: 'Show me',
|
||||
none: '無',
|
||||
},
|
||||
|
||||
copy_to_clipboard_toast: {
|
||||
unsupported: {
|
||||
title: '複製失敗',
|
||||
@@ -110,14 +112,11 @@ export const zh_TW: LanguageTranslation = {
|
||||
copied: '已複製!',
|
||||
|
||||
side_panel: {
|
||||
schema: 'Schema:',
|
||||
filter_by_schema: '依 Schema 篩選',
|
||||
search_schema: '搜尋 Schema...',
|
||||
no_schemas_found: '未找到 Schema。',
|
||||
view_all_options: '顯示所有選項...',
|
||||
tables_section: {
|
||||
tables: '表格',
|
||||
add_table: '新增表格',
|
||||
add_view: '新增檢視',
|
||||
filter: '篩選',
|
||||
collapse: '全部摺疊',
|
||||
// TODO: Translate
|
||||
@@ -143,6 +142,7 @@ export const zh_TW: LanguageTranslation = {
|
||||
field_actions: {
|
||||
title: '欄位屬性',
|
||||
unique: '唯一',
|
||||
auto_increment: '自動遞增',
|
||||
comments: '註解',
|
||||
no_comments: '無註解',
|
||||
delete_field: '刪除欄位',
|
||||
@@ -151,11 +151,14 @@ export const zh_TW: LanguageTranslation = {
|
||||
no_default: 'No default',
|
||||
// TODO: Translate
|
||||
character_length: 'Max Length',
|
||||
precision: '精度',
|
||||
scale: '小數位',
|
||||
},
|
||||
index_actions: {
|
||||
title: '索引屬性',
|
||||
name: '名稱',
|
||||
unique: '唯一',
|
||||
index_type: '索引類型',
|
||||
delete_index: '刪除索引',
|
||||
},
|
||||
table_actions: {
|
||||
@@ -172,12 +175,15 @@ export const zh_TW: LanguageTranslation = {
|
||||
description: '請新增表格以開始',
|
||||
},
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: '關聯',
|
||||
refs_section: {
|
||||
refs: 'Refs',
|
||||
filter: '篩選',
|
||||
add_relationship: '新增關聯',
|
||||
collapse: '全部摺疊',
|
||||
add_relationship: '新增關聯',
|
||||
relationships: '關聯',
|
||||
dependencies: '相依性',
|
||||
relationship: {
|
||||
relationship: '關聯',
|
||||
primary: '主表格',
|
||||
foreign: '參照表格',
|
||||
cardinality: '基數',
|
||||
@@ -187,16 +193,8 @@ export const zh_TW: LanguageTranslation = {
|
||||
delete_relationship: '刪除',
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: '尚無關聯',
|
||||
description: '請新增關聯以連接表格',
|
||||
},
|
||||
},
|
||||
dependencies_section: {
|
||||
dependencies: '相依性',
|
||||
filter: '篩選',
|
||||
collapse: '全部摺疊',
|
||||
dependency: {
|
||||
dependency: '相依性',
|
||||
table: '表格',
|
||||
dependent_table: '相依檢視',
|
||||
delete_dependency: '刪除',
|
||||
@@ -206,8 +204,8 @@ export const zh_TW: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
empty_state: {
|
||||
title: '尚無相依性',
|
||||
description: '請建立檢視以開始',
|
||||
title: '尚無關聯',
|
||||
description: '請建立關聯以開始',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -247,12 +245,16 @@ export const zh_TW: LanguageTranslation = {
|
||||
enum_values: 'Enum Values',
|
||||
composite_fields: 'Fields',
|
||||
no_fields: 'No fields defined',
|
||||
no_values: '沒有定義列舉值',
|
||||
field_name_placeholder: 'Field name',
|
||||
field_type_placeholder: 'Select type',
|
||||
add_field: 'Add Field',
|
||||
no_fields_tooltip: 'No fields defined for this custom type',
|
||||
custom_type_actions: {
|
||||
title: 'Actions',
|
||||
highlight_fields: 'Highlight Fields',
|
||||
delete_custom_type: 'Delete',
|
||||
clear_field_highlight: 'Clear Highlight',
|
||||
},
|
||||
delete_custom_type: 'Delete Type',
|
||||
},
|
||||
@@ -266,7 +268,11 @@ export const zh_TW: LanguageTranslation = {
|
||||
show_all: '顯示全部',
|
||||
undo: '復原',
|
||||
redo: '重做',
|
||||
reorder_diagram: '重新排列圖表',
|
||||
reorder_diagram: '自動排列圖表',
|
||||
// TODO: Translate
|
||||
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||
custom_type_highlight_tooltip:
|
||||
'Highlighting "{{typeName}}" - Click to clear',
|
||||
highlight_overlapping_tables: '突出顯示重疊表格',
|
||||
// TODO: Translate
|
||||
filter: 'Filter Tables',
|
||||
@@ -305,7 +311,7 @@ export const zh_TW: LanguageTranslation = {
|
||||
},
|
||||
|
||||
open_diagram_dialog: {
|
||||
title: '開啟圖表',
|
||||
title: '開啟資料庫',
|
||||
description: '請從以下列表中選擇一個圖表。',
|
||||
table_columns: {
|
||||
name: '名稱',
|
||||
@@ -315,6 +321,12 @@ export const zh_TW: LanguageTranslation = {
|
||||
},
|
||||
cancel: '取消',
|
||||
open: '開啟',
|
||||
|
||||
diagram_actions: {
|
||||
open: '開啟',
|
||||
duplicate: '複製',
|
||||
delete: '刪除',
|
||||
},
|
||||
},
|
||||
|
||||
export_sql_dialog: {
|
||||
@@ -399,6 +411,14 @@ export const zh_TW: LanguageTranslation = {
|
||||
confirm: '變更',
|
||||
},
|
||||
|
||||
create_table_schema_dialog: {
|
||||
title: '建立新 Schema',
|
||||
description:
|
||||
'尚未存在任何 Schema。建立您的第一個 Schema 來組織您的表格。',
|
||||
create: '建立',
|
||||
cancel: '取消',
|
||||
},
|
||||
|
||||
star_us_dialog: {
|
||||
title: '協助我們改善!',
|
||||
description: '請在 GitHub 上給我們一顆星,只需點擊一下!',
|
||||
@@ -453,6 +473,7 @@ export const zh_TW: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: '新建表格',
|
||||
new_view: '新檢視',
|
||||
new_relationship: '新建關聯',
|
||||
// TODO: Translate
|
||||
new_area: 'New Area',
|
||||
@@ -474,6 +495,9 @@ export const zh_TW: LanguageTranslation = {
|
||||
language_select: {
|
||||
change_language: '變更語言',
|
||||
},
|
||||
|
||||
on: '開啟',
|
||||
off: '關閉',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -18,4 +18,7 @@
|
||||
|
||||
.marker-definitions {
|
||||
}
|
||||
|
||||
.nodrag {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { DBCustomType } from './domain';
|
||||
import type { Area } from './domain/area';
|
||||
import type { DBDependency } from './domain/db-dependency';
|
||||
import type { DBField } from './domain/db-field';
|
||||
@@ -48,6 +49,10 @@ const generateIdsMapFromDiagram = (
|
||||
idsMap.set(area.id, generateId());
|
||||
});
|
||||
|
||||
diagram.customTypes?.forEach((customType) => {
|
||||
idsMap.set(customType.id, generateId());
|
||||
});
|
||||
|
||||
return idsMap;
|
||||
};
|
||||
|
||||
@@ -213,6 +218,22 @@ export const cloneDiagram = (
|
||||
})
|
||||
.filter((area): area is Area => area !== null) ?? [];
|
||||
|
||||
const customTypes: DBCustomType[] =
|
||||
diagram.customTypes
|
||||
?.map((customType) => {
|
||||
const id = getNewId(customType.id);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...customType,
|
||||
id,
|
||||
} satisfies DBCustomType;
|
||||
})
|
||||
.filter(
|
||||
(customType): customType is DBCustomType => customType !== null
|
||||
) ?? [];
|
||||
|
||||
return {
|
||||
diagram: {
|
||||
...diagram,
|
||||
@@ -221,6 +242,7 @@ export const cloneDiagram = (
|
||||
relationships,
|
||||
tables,
|
||||
areas,
|
||||
customTypes,
|
||||
createdAt: diagram.createdAt
|
||||
? new Date(diagram.createdAt)
|
||||
: new Date(),
|
||||
|
||||
@@ -19,3 +19,5 @@ export const randomColor = () => {
|
||||
|
||||
export const viewColor = '#b0b0b0';
|
||||
export const materializedViewColor = '#7d7d7d';
|
||||
export const defaultTableColor = '#8eb7ff';
|
||||
export const defaultAreaColor = '#b067e9';
|
||||
|
||||
@@ -48,18 +48,30 @@ export const clickhouseDataTypes: readonly DataTypeData[] = [
|
||||
{ name: 'mediumblob', id: 'mediumblob' },
|
||||
{ name: 'tinyblob', id: 'tinyblob' },
|
||||
{ name: 'blob', id: 'blob' },
|
||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true },
|
||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'varchar',
|
||||
id: 'varchar',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
|
||||
{ name: 'char large object', id: 'char_large_object' },
|
||||
{ name: 'char varying', id: 'char_varying', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'char varying',
|
||||
id: 'char_varying',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'character large object', id: 'character_large_object' },
|
||||
{
|
||||
name: 'character varying',
|
||||
id: 'character_varying',
|
||||
hasCharMaxLength: true,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'nchar large object', id: 'nchar_large_object' },
|
||||
{ name: 'nchar varying', id: 'nchar_varying', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'nchar varying',
|
||||
id: 'nchar_varying',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'national character large object',
|
||||
id: 'national_character_large_object',
|
||||
@@ -67,22 +79,34 @@ export const clickhouseDataTypes: readonly DataTypeData[] = [
|
||||
{
|
||||
name: 'national character varying',
|
||||
id: 'national_character_varying',
|
||||
hasCharMaxLength: true,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'national char varying',
|
||||
id: 'national_char_varying',
|
||||
hasCharMaxLength: true,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'national character',
|
||||
id: 'national_character',
|
||||
hasCharMaxLength: true,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'national char',
|
||||
id: 'national_char',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'national char', id: 'national_char', hasCharMaxLength: true },
|
||||
{ name: 'binary large object', id: 'binary_large_object' },
|
||||
{ name: 'binary varying', id: 'binary_varying', hasCharMaxLength: true },
|
||||
{ name: 'fixedstring', id: 'fixedstring', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'binary varying',
|
||||
id: 'binary_varying',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'fixedstring',
|
||||
id: 'fixedstring',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'string', id: 'string' },
|
||||
|
||||
// Date Types
|
||||
|
||||
@@ -14,9 +14,23 @@ export interface DataType {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DataTypeData extends DataType {
|
||||
export interface FieldAttributeRange {
|
||||
max: number;
|
||||
min: number;
|
||||
default: number;
|
||||
}
|
||||
|
||||
interface FieldAttributes {
|
||||
hasCharMaxLength?: boolean;
|
||||
hasCharMaxLengthOption?: boolean;
|
||||
precision?: FieldAttributeRange;
|
||||
scale?: FieldAttributeRange;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
export interface DataTypeData extends DataType {
|
||||
usageLevel?: 1 | 2; // Level 1 is most common, Level 2 is second most common
|
||||
fieldAttributes?: FieldAttributes;
|
||||
}
|
||||
|
||||
export const dataTypeSchema: z.ZodType<DataType> = z.object({
|
||||
@@ -132,3 +146,22 @@ export const findDataTypeDataById = (
|
||||
|
||||
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());
|
||||
};
|
||||
|
||||
@@ -2,7 +2,12 @@ import type { DataTypeData } from './data-types';
|
||||
|
||||
export const genericDataTypes: readonly DataTypeData[] = [
|
||||
// Level 1 - Most commonly used types
|
||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||
{
|
||||
name: 'varchar',
|
||||
id: 'varchar',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
usageLevel: 1,
|
||||
},
|
||||
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||
@@ -10,23 +15,62 @@ export const genericDataTypes: readonly DataTypeData[] = [
|
||||
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||
|
||||
// Level 2 - Second most common types
|
||||
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||
{
|
||||
name: 'decimal',
|
||||
id: 'decimal',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 999,
|
||||
min: 1,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 999,
|
||||
min: 0,
|
||||
default: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'datetime', id: 'datetime', usageLevel: 2 },
|
||||
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||
{ name: 'uuid', id: 'uuid', usageLevel: 2 },
|
||||
|
||||
// Less common types
|
||||
{ name: 'bigint', id: 'bigint' },
|
||||
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'binary',
|
||||
id: 'binary',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'blob', id: 'blob' },
|
||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||
{ name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
|
||||
{ name: 'double', id: 'double' },
|
||||
{ name: 'enum', id: 'enum' },
|
||||
{ name: 'float', id: 'float' },
|
||||
{ name: 'numeric', id: 'numeric' },
|
||||
{
|
||||
name: 'numeric',
|
||||
id: 'numeric',
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 999,
|
||||
min: 1,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 999,
|
||||
min: 0,
|
||||
default: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'real', id: 'real' },
|
||||
{ name: 'set', id: 'set' },
|
||||
{ name: 'smallint', id: 'smallint' },
|
||||
{ name: 'time', id: 'time' },
|
||||
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'varbinary',
|
||||
id: 'varbinary',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -4,12 +4,32 @@ export const mariadbDataTypes: readonly DataTypeData[] = [
|
||||
// Level 1 - Most commonly used types
|
||||
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||
{ name: 'bigint', id: 'bigint', usageLevel: 1 },
|
||||
{ name: 'decimal', id: 'decimal', usageLevel: 1 },
|
||||
{
|
||||
name: 'decimal',
|
||||
id: 'decimal',
|
||||
usageLevel: 1,
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 65,
|
||||
min: 1,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 30,
|
||||
min: 0,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||
{ name: 'datetime', id: 'datetime', usageLevel: 1 },
|
||||
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||
{
|
||||
name: 'varchar',
|
||||
id: 'varchar',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||
|
||||
// Level 2 - Second most common types
|
||||
@@ -20,16 +40,39 @@ export const mariadbDataTypes: readonly DataTypeData[] = [
|
||||
{ name: 'tinyint', id: 'tinyint' },
|
||||
{ name: 'smallint', id: 'smallint' },
|
||||
{ name: 'mediumint', id: 'mediumint' },
|
||||
{ name: 'numeric', id: 'numeric' },
|
||||
{
|
||||
name: 'numeric',
|
||||
id: 'numeric',
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 65,
|
||||
min: 1,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 30,
|
||||
min: 0,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'float', id: 'float' },
|
||||
{ name: 'double', id: 'double' },
|
||||
{ name: 'bit', id: 'bit' },
|
||||
{ name: 'bool', id: 'bool' },
|
||||
{ name: 'time', id: 'time' },
|
||||
{ name: 'year', id: 'year' },
|
||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
||||
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
||||
{ name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
|
||||
{
|
||||
name: 'binary',
|
||||
id: 'binary',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'varbinary',
|
||||
id: 'varbinary',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'tinyblob', id: 'tinyblob' },
|
||||
{ name: 'blob', id: 'blob' },
|
||||
{ name: 'mediumblob', id: 'mediumblob' },
|
||||
|
||||
@@ -3,7 +3,12 @@ import type { DataTypeData } from './data-types';
|
||||
export const mysqlDataTypes: readonly DataTypeData[] = [
|
||||
// Level 1 - Most commonly used types
|
||||
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||
{
|
||||
name: 'varchar',
|
||||
id: 'varchar',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
usageLevel: 1,
|
||||
},
|
||||
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||
@@ -11,7 +16,23 @@ export const mysqlDataTypes: readonly DataTypeData[] = [
|
||||
|
||||
// Level 2 - Second most common types
|
||||
{ name: 'bigint', id: 'bigint', usageLevel: 2 },
|
||||
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||
{
|
||||
name: 'decimal',
|
||||
id: 'decimal',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 65,
|
||||
min: 1,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 30,
|
||||
min: 0,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'datetime', id: 'datetime', usageLevel: 2 },
|
||||
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||
|
||||
@@ -22,7 +43,7 @@ export const mysqlDataTypes: readonly DataTypeData[] = [
|
||||
{ name: 'float', id: 'float' },
|
||||
{ name: 'double', id: 'double' },
|
||||
{ name: 'bit', id: 'bit' },
|
||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||
{ name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
|
||||
{ name: 'tinytext', id: 'tinytext' },
|
||||
{ name: 'mediumtext', id: 'mediumtext' },
|
||||
{ name: 'longtext', id: 'longtext' },
|
||||
|
||||
@@ -2,15 +2,30 @@ import type { DataTypeData } from './data-types';
|
||||
|
||||
export const oracleDataTypes: readonly DataTypeData[] = [
|
||||
// Character types
|
||||
{ name: 'VARCHAR2', id: 'varchar2', usageLevel: 1, hasCharMaxLength: true },
|
||||
{
|
||||
name: 'VARCHAR2',
|
||||
id: 'varchar2',
|
||||
usageLevel: 1,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'NVARCHAR2',
|
||||
id: 'nvarchar2',
|
||||
usageLevel: 1,
|
||||
hasCharMaxLength: true,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'CHAR',
|
||||
id: 'char',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'NCHAR',
|
||||
id: 'nchar',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'CHAR', id: 'char', usageLevel: 2, hasCharMaxLength: true },
|
||||
{ name: 'NCHAR', id: 'nchar', usageLevel: 2, hasCharMaxLength: true },
|
||||
{ name: 'CLOB', id: 'clob', usageLevel: 2 },
|
||||
{ name: 'NCLOB', id: 'nclob', usageLevel: 2 },
|
||||
|
||||
@@ -49,7 +64,12 @@ export const oracleDataTypes: readonly DataTypeData[] = [
|
||||
{ name: 'BFILE', id: 'bfile', usageLevel: 2 },
|
||||
|
||||
// Other types
|
||||
{ name: 'RAW', id: 'raw', usageLevel: 2, hasCharMaxLength: true },
|
||||
{
|
||||
name: 'RAW',
|
||||
id: 'raw',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'LONG RAW', id: 'long_raw', usageLevel: 2 },
|
||||
{ name: 'ROWID', id: 'rowid', usageLevel: 2 },
|
||||
{ name: 'UROWID', id: 'urowid', usageLevel: 2 },
|
||||
|
||||
@@ -3,7 +3,12 @@ import type { DataTypeData } from './data-types';
|
||||
export const postgresDataTypes: readonly DataTypeData[] = [
|
||||
// Level 1 - Most commonly used types
|
||||
{ name: 'integer', id: 'integer', usageLevel: 1 },
|
||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||
{
|
||||
name: 'varchar',
|
||||
id: 'varchar',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
usageLevel: 1,
|
||||
},
|
||||
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||
@@ -11,7 +16,23 @@ export const postgresDataTypes: readonly DataTypeData[] = [
|
||||
|
||||
// Level 2 - Second most common types
|
||||
{ name: 'bigint', id: 'bigint', usageLevel: 2 },
|
||||
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||
{
|
||||
name: 'decimal',
|
||||
id: 'decimal',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 131072,
|
||||
min: 0,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 16383,
|
||||
min: 0,
|
||||
default: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'serial', id: 'serial', usageLevel: 2 },
|
||||
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||
{ name: 'jsonb', id: 'jsonb', usageLevel: 2 },
|
||||
@@ -23,18 +44,33 @@ export const postgresDataTypes: readonly DataTypeData[] = [
|
||||
},
|
||||
|
||||
// Less common types
|
||||
{ name: 'numeric', id: 'numeric' },
|
||||
{
|
||||
name: 'numeric',
|
||||
id: 'numeric',
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 131072,
|
||||
min: 0,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 16383,
|
||||
min: 0,
|
||||
default: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'real', id: 'real' },
|
||||
{ name: 'double precision', id: 'double_precision' },
|
||||
{ name: 'smallserial', id: 'smallserial' },
|
||||
{ name: 'bigserial', id: 'bigserial' },
|
||||
{ name: 'money', id: 'money' },
|
||||
{ name: 'smallint', id: 'smallint' },
|
||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||
{ name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
|
||||
{
|
||||
name: 'character varying',
|
||||
id: 'character_varying',
|
||||
hasCharMaxLength: true,
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{ name: 'time', id: 'time' },
|
||||
{ name: 'timestamp without time zone', id: 'timestamp_without_time_zone' },
|
||||
|
||||
@@ -4,32 +4,93 @@ export const sqlServerDataTypes: readonly DataTypeData[] = [
|
||||
// Level 1 - Most commonly used types
|
||||
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||
{ name: 'bit', id: 'bit', usageLevel: 1 },
|
||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||
{ name: 'nvarchar', id: 'nvarchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||
{
|
||||
name: 'varchar',
|
||||
id: 'varchar',
|
||||
fieldAttributes: {
|
||||
hasCharMaxLength: true,
|
||||
hasCharMaxLengthOption: true,
|
||||
maxLength: 8000,
|
||||
},
|
||||
usageLevel: 1,
|
||||
},
|
||||
{
|
||||
name: 'nvarchar',
|
||||
id: 'nvarchar',
|
||||
fieldAttributes: {
|
||||
hasCharMaxLength: true,
|
||||
hasCharMaxLengthOption: true,
|
||||
maxLength: 4000,
|
||||
},
|
||||
usageLevel: 1,
|
||||
},
|
||||
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||
{ name: 'datetime', id: 'datetime', usageLevel: 1 },
|
||||
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||
|
||||
// Level 2 - Second most common types
|
||||
{ name: 'bigint', id: 'bigint', usageLevel: 2 },
|
||||
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||
{
|
||||
name: 'decimal',
|
||||
id: 'decimal',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 38,
|
||||
min: 1,
|
||||
default: 18,
|
||||
},
|
||||
scale: {
|
||||
max: 38,
|
||||
min: 0,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'datetime2', id: 'datetime2', usageLevel: 2 },
|
||||
{ name: 'uniqueidentifier', id: 'uniqueidentifier', usageLevel: 2 },
|
||||
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||
|
||||
// Less common types
|
||||
{ name: 'numeric', id: 'numeric' },
|
||||
{
|
||||
name: 'numeric',
|
||||
id: 'numeric',
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 38,
|
||||
min: 1,
|
||||
default: 18,
|
||||
},
|
||||
scale: {
|
||||
max: 38,
|
||||
min: 0,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: 'smallint', id: 'smallint' },
|
||||
{ name: 'smallmoney', id: 'smallmoney' },
|
||||
{ name: 'tinyint', id: 'tinyint' },
|
||||
{ name: 'money', id: 'money' },
|
||||
{ name: 'float', id: 'float' },
|
||||
{ name: 'real', id: 'real' },
|
||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||
{ name: 'nchar', id: 'nchar', hasCharMaxLength: true },
|
||||
{ name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
|
||||
{ name: 'nchar', id: 'nchar', fieldAttributes: { hasCharMaxLength: true } },
|
||||
{ name: 'ntext', id: 'ntext' },
|
||||
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
||||
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'binary',
|
||||
id: 'binary',
|
||||
fieldAttributes: { hasCharMaxLength: true },
|
||||
},
|
||||
{
|
||||
name: 'varbinary',
|
||||
id: 'varbinary',
|
||||
fieldAttributes: {
|
||||
hasCharMaxLength: true,
|
||||
hasCharMaxLengthOption: true,
|
||||
maxLength: 8000,
|
||||
},
|
||||
},
|
||||
{ name: 'image', id: 'image' },
|
||||
{ name: 'datetimeoffset', id: 'datetimeoffset' },
|
||||
{ name: 'smalldatetime', id: 'smalldatetime' },
|
||||
|
||||
@@ -10,25 +10,48 @@ export const sqliteDataTypes: readonly DataTypeData[] = [
|
||||
|
||||
// SQLite type aliases and common types
|
||||
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||
{ name: 'datetime', id: 'datetime', usageLevel: 1 },
|
||||
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||
{
|
||||
name: 'varchar',
|
||||
id: 'varchar',
|
||||
fieldAttributes: {
|
||||
hasCharMaxLength: true,
|
||||
},
|
||||
usageLevel: 1,
|
||||
},
|
||||
{
|
||||
name: 'timestamp',
|
||||
id: 'timestamp',
|
||||
usageLevel: 1,
|
||||
},
|
||||
|
||||
// Level 2 - Second most common types
|
||||
{ name: 'numeric', id: 'numeric', usageLevel: 2 },
|
||||
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||
{ name: 'float', id: 'float', usageLevel: 2 },
|
||||
{
|
||||
name: 'decimal',
|
||||
id: 'decimal',
|
||||
usageLevel: 2,
|
||||
},
|
||||
{ name: 'double', id: 'double', usageLevel: 2 },
|
||||
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||
|
||||
// Less common types (all map to SQLite storage classes)
|
||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||
{
|
||||
name: 'char',
|
||||
id: 'char',
|
||||
fieldAttributes: {
|
||||
hasCharMaxLength: true,
|
||||
},
|
||||
usageLevel: 2,
|
||||
},
|
||||
{ name: 'binary', id: 'binary' },
|
||||
{ name: 'varbinary', id: 'varbinary' },
|
||||
{ name: 'smallint', id: 'smallint' },
|
||||
{ name: 'bigint', id: 'bigint' },
|
||||
{ name: 'bool', id: 'bool' },
|
||||
{ name: 'boolean', id: 'boolean' }, // Added for smartquery compatibility
|
||||
{ name: 'time', id: 'time' },
|
||||
{ name: 'date', id: 'date' }, // Added for smartquery compatibility
|
||||
{ name: 'datetime', id: 'datetime' }, // Added for smartquery compatibility
|
||||
] as const;
|
||||
|
||||
@@ -4,4 +4,5 @@ export const defaultSchemas: { [key in DatabaseType]?: string } = {
|
||||
[DatabaseType.POSTGRESQL]: 'public',
|
||||
[DatabaseType.SQL_SERVER]: 'dbo',
|
||||
[DatabaseType.CLICKHOUSE]: 'default',
|
||||
[DatabaseType.COCKROACHDB]: 'public',
|
||||
};
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
import {
|
||||
exportFieldComment,
|
||||
formatMSSQLTableComment,
|
||||
isFunction,
|
||||
isKeyword,
|
||||
strHasQuotes,
|
||||
} from './common';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
|
||||
function parseMSSQLDefault(field: DBField): string {
|
||||
if (!field.default) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let defaultValue = field.default.trim();
|
||||
|
||||
// Remove type casting for SQL Server
|
||||
defaultValue = defaultValue.split('::')[0];
|
||||
|
||||
// Handle nextval sequences for SQL Server
|
||||
if (defaultValue.includes('nextval')) {
|
||||
return 'IDENTITY(1,1)';
|
||||
}
|
||||
|
||||
// Special handling for SQL Server DEFAULT values
|
||||
if (defaultValue.match(/^\(\(.*\)\)$/)) {
|
||||
// Handle ((0)), ((0.00)) style defaults
|
||||
return defaultValue.replace(/^\(\(|\)\)$/g, '');
|
||||
} else if (defaultValue.match(/^\(N'.*'\)$/)) {
|
||||
// Handle (N'value') style defaults
|
||||
const innerValue = defaultValue.replace(/^\(N'|'\)$/g, '');
|
||||
return `N'${innerValue}'`;
|
||||
} else if (defaultValue.match(/^\(NULL\)$/i)) {
|
||||
// Handle (NULL) defaults
|
||||
return 'NULL';
|
||||
} else if (defaultValue.match(/^\(getdate\(\)\)$/i)) {
|
||||
// Handle (getdate()) defaults
|
||||
return 'getdate()';
|
||||
} else if (defaultValue.match(/^\('?\*'?\)$/i) || defaultValue === '*') {
|
||||
// Handle ('*') or (*) or * defaults - common for "all" values
|
||||
return "N'*'";
|
||||
} else if (defaultValue.match(/^\((['"])(.*)\1\)$/)) {
|
||||
// Handle ('value') or ("value") style defaults
|
||||
const matches = defaultValue.match(/^\((['"])(.*)\1\)$/);
|
||||
return matches ? `N'${matches[2]}'` : defaultValue;
|
||||
}
|
||||
|
||||
// Handle special characters that could be interpreted as operators
|
||||
const sqlServerSpecialChars = /[*+\-/%&|^!=<>~]/;
|
||||
if (sqlServerSpecialChars.test(defaultValue)) {
|
||||
// If the value contains special characters and isn't already properly quoted
|
||||
if (
|
||||
!strHasQuotes(defaultValue) &&
|
||||
!isFunction(defaultValue) &&
|
||||
!isKeyword(defaultValue)
|
||||
) {
|
||||
return `N'${defaultValue.replace(/'/g, "''")}'`;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
strHasQuotes(defaultValue) ||
|
||||
isFunction(defaultValue) ||
|
||||
isKeyword(defaultValue) ||
|
||||
/^-?\d+(\.\d+)?$/.test(defaultValue)
|
||||
) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return `'${defaultValue}'`;
|
||||
}
|
||||
|
||||
export function exportMSSQL(diagram: Diagram): string {
|
||||
if (!diagram.tables || !diagram.relationships) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tables = diagram.tables;
|
||||
const relationships = diagram.relationships;
|
||||
|
||||
// Create CREATE SCHEMA statements for all schemas
|
||||
let sqlScript = '';
|
||||
const schemas = new Set<string>();
|
||||
|
||||
tables.forEach((table) => {
|
||||
if (table.schema) {
|
||||
schemas.add(table.schema);
|
||||
}
|
||||
});
|
||||
|
||||
// Add schema creation statements
|
||||
schemas.forEach((schema) => {
|
||||
sqlScript += `IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '${schema}')\nBEGIN\n EXEC('CREATE SCHEMA [${schema}]');\nEND;\n\n`;
|
||||
});
|
||||
|
||||
// Generate table creation SQL
|
||||
sqlScript += tables
|
||||
.map((table: DBTable) => {
|
||||
// Skip views
|
||||
if (table.isView) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tableName = table.schema
|
||||
? `[${table.schema}].[${table.name}]`
|
||||
: `[${table.name}]`;
|
||||
|
||||
return `${
|
||||
table.comments ? formatMSSQLTableComment(table.comments) : ''
|
||||
}CREATE TABLE ${tableName} (\n${table.fields
|
||||
.map((field: DBField) => {
|
||||
const fieldName = `[${field.name}]`;
|
||||
const typeName = field.type.name;
|
||||
|
||||
// Handle SQL Server specific type formatting
|
||||
let typeWithSize = typeName;
|
||||
if (field.characterMaximumLength) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'varchar' ||
|
||||
typeName.toLowerCase() === 'nvarchar' ||
|
||||
typeName.toLowerCase() === 'char' ||
|
||||
typeName.toLowerCase() === 'nchar'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.characterMaximumLength})`;
|
||||
}
|
||||
} else if (field.precision && field.scale) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'decimal' ||
|
||||
typeName.toLowerCase() === 'numeric'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.precision}, ${field.scale})`;
|
||||
}
|
||||
} else if (field.precision) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'decimal' ||
|
||||
typeName.toLowerCase() === 'numeric'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.precision})`;
|
||||
}
|
||||
}
|
||||
|
||||
const notNull = field.nullable ? '' : ' NOT NULL';
|
||||
|
||||
// Check if identity column
|
||||
const identity = field.default
|
||||
?.toLowerCase()
|
||||
.includes('identity')
|
||||
? ' IDENTITY(1,1)'
|
||||
: '';
|
||||
|
||||
const unique =
|
||||
!field.primaryKey && field.unique ? ' UNIQUE' : '';
|
||||
|
||||
// Handle default value using SQL Server specific parser
|
||||
const defaultValue =
|
||||
field.default &&
|
||||
!field.default.toLowerCase().includes('identity')
|
||||
? ` DEFAULT ${parseMSSQLDefault(field)}`
|
||||
: '';
|
||||
|
||||
// Do not add PRIMARY KEY as a column constraint - will add as table constraint
|
||||
return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${typeWithSize}${notNull}${identity}${unique}${defaultValue}`;
|
||||
})
|
||||
.join(',\n')}${
|
||||
table.fields.filter((f) => f.primaryKey).length > 0
|
||||
? `,\n PRIMARY KEY (${table.fields
|
||||
.filter((f) => f.primaryKey)
|
||||
.map((f) => `[${f.name}]`)
|
||||
.join(', ')})`
|
||||
: ''
|
||||
}\n);\n\n${table.indexes
|
||||
.map((index) => {
|
||||
const indexName = table.schema
|
||||
? `[${table.schema}_${index.name}]`
|
||||
: `[${index.name}]`;
|
||||
const indexFields = index.fieldIds
|
||||
.map((fieldId) => {
|
||||
const field = table.fields.find(
|
||||
(f) => f.id === fieldId
|
||||
);
|
||||
return field ? `[${field.name}]` : '';
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
// SQL Server has a limit of 32 columns in an index
|
||||
if (indexFields.length > 32) {
|
||||
const warningComment = `/* WARNING: This index originally had ${indexFields.length} columns. It has been truncated to 32 columns due to SQL Server's index column limit. */\n`;
|
||||
console.warn(
|
||||
`Warning: Index ${indexName} on table ${tableName} has ${indexFields.length} columns. SQL Server limits indexes to 32 columns. The index will be truncated.`
|
||||
);
|
||||
indexFields.length = 32;
|
||||
return indexFields.length > 0
|
||||
? `${warningComment}CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName}\nON ${tableName} (${indexFields.join(', ')});\n\n`
|
||||
: '';
|
||||
}
|
||||
|
||||
return indexFields.length > 0
|
||||
? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName}\nON ${tableName} (${indexFields.join(', ')});\n\n`
|
||||
: '';
|
||||
})
|
||||
.join('')}`;
|
||||
})
|
||||
.filter(Boolean) // Remove empty strings (views)
|
||||
.join('\n');
|
||||
|
||||
// Generate foreign keys
|
||||
sqlScript += `\n${relationships
|
||||
.map((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
|
||||
) {
|
||||
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 '';
|
||||
}
|
||||
|
||||
const fkTableName = fkTable.schema
|
||||
? `[${fkTable.schema}].[${fkTable.name}]`
|
||||
: `[${fkTable.name}]`;
|
||||
const refTableName = refTable.schema
|
||||
? `[${refTable.schema}].[${refTable.name}]`
|
||||
: `[${refTable.name}]`;
|
||||
|
||||
return `ALTER TABLE ${fkTableName}\nADD CONSTRAINT [${r.name}] FOREIGN KEY([${fkField.name}]) REFERENCES ${refTableName}([${refField.name}]);\n`;
|
||||
})
|
||||
.filter(Boolean) // Remove empty strings
|
||||
.join('\n')}`;
|
||||
|
||||
return sqlScript;
|
||||
}
|
||||
@@ -1,485 +0,0 @@
|
||||
import {
|
||||
exportFieldComment,
|
||||
escapeSQLComment,
|
||||
formatTableComment,
|
||||
isFunction,
|
||||
isKeyword,
|
||||
strHasQuotes,
|
||||
} from './common';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
|
||||
function parseMySQLDefault(field: DBField): string {
|
||||
if (!field.default) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const defaultValue = field.default.trim();
|
||||
|
||||
// Handle specific MySQL default values
|
||||
if (
|
||||
defaultValue.toLowerCase() === 'now()' ||
|
||||
defaultValue.toLowerCase() === 'current_timestamp'
|
||||
) {
|
||||
return 'CURRENT_TIMESTAMP';
|
||||
}
|
||||
|
||||
// Handle MySQL auto-increment, which is handled via AUTO_INCREMENT
|
||||
if (
|
||||
defaultValue.toLowerCase().includes('identity') ||
|
||||
defaultValue.toLowerCase().includes('autoincrement') ||
|
||||
defaultValue.includes('nextval')
|
||||
) {
|
||||
return ''; // MySQL handles this with AUTO_INCREMENT
|
||||
}
|
||||
|
||||
// If it's a function call, convert to MySQL equivalents
|
||||
if (isFunction(defaultValue)) {
|
||||
// Map common PostgreSQL/MSSQL functions to MySQL equivalents
|
||||
if (
|
||||
defaultValue.toLowerCase().includes('newid()') ||
|
||||
defaultValue.toLowerCase().includes('uuid()')
|
||||
) {
|
||||
return 'UUID()';
|
||||
}
|
||||
|
||||
// For functions we can't translate, return as is (MySQL might not support them)
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it's a keyword, keep it as is
|
||||
if (isKeyword(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it already has quotes, keep it as is
|
||||
if (strHasQuotes(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it's a number, keep it as is
|
||||
if (/^-?\d+(\.\d+)?$/.test(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// For other cases, add quotes
|
||||
return `'${defaultValue.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
// Map problematic types to MySQL compatible types
|
||||
function mapMySQLType(typeName: string): string {
|
||||
typeName = typeName.toLowerCase();
|
||||
|
||||
// Map common types to MySQL type system
|
||||
switch (typeName) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return 'INT';
|
||||
|
||||
case 'smallint':
|
||||
return 'SMALLINT';
|
||||
|
||||
case 'bigint':
|
||||
return 'BIGINT';
|
||||
|
||||
case 'decimal':
|
||||
case 'numeric':
|
||||
return 'DECIMAL';
|
||||
|
||||
case 'float':
|
||||
return 'FLOAT';
|
||||
|
||||
case 'double':
|
||||
case 'real':
|
||||
return 'DOUBLE';
|
||||
|
||||
case 'char':
|
||||
case 'character':
|
||||
return 'CHAR';
|
||||
|
||||
case 'varchar':
|
||||
case 'character varying':
|
||||
case 'nvarchar':
|
||||
return 'VARCHAR';
|
||||
|
||||
case 'text':
|
||||
case 'ntext':
|
||||
return 'TEXT';
|
||||
|
||||
case 'longtext':
|
||||
return 'LONGTEXT';
|
||||
|
||||
case 'mediumtext':
|
||||
return 'MEDIUMTEXT';
|
||||
|
||||
case 'tinytext':
|
||||
return 'TINYTEXT';
|
||||
|
||||
case 'date':
|
||||
return 'DATE';
|
||||
|
||||
case 'datetime':
|
||||
case 'timestamp':
|
||||
case 'datetime2':
|
||||
return 'DATETIME';
|
||||
|
||||
case 'time':
|
||||
return 'TIME';
|
||||
|
||||
case 'blob':
|
||||
case 'binary':
|
||||
return 'BLOB';
|
||||
|
||||
case 'varbinary':
|
||||
return 'VARBINARY';
|
||||
|
||||
case 'bit':
|
||||
return 'BIT';
|
||||
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
return 'TINYINT(1)'; // MySQL uses TINYINT(1) for boolean
|
||||
|
||||
case 'enum':
|
||||
return 'VARCHAR(50)'; // Convert ENUM to VARCHAR instead of assuming values
|
||||
|
||||
case 'json':
|
||||
case 'jsonb':
|
||||
return 'JSON'; // MySQL has JSON type since 5.7.8
|
||||
|
||||
case 'uuid':
|
||||
return 'CHAR(36)'; // MySQL doesn't have a UUID type, use CHAR(36)
|
||||
|
||||
case 'geometry':
|
||||
case 'geography':
|
||||
return 'GEOMETRY'; // If MySQL has spatial extensions
|
||||
|
||||
case 'array':
|
||||
case 'user-defined':
|
||||
return 'JSON'; // Use JSON for complex types like arrays or user-defined
|
||||
}
|
||||
|
||||
// If type has array notation (ends with []), treat as JSON
|
||||
if (typeName.endsWith('[]')) {
|
||||
return 'JSON';
|
||||
}
|
||||
|
||||
// For any other types, default to original type
|
||||
return typeName;
|
||||
}
|
||||
|
||||
export function exportMySQL(diagram: Diagram): string {
|
||||
if (!diagram.tables || !diagram.relationships) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tables = diagram.tables;
|
||||
const relationships = diagram.relationships;
|
||||
|
||||
// Start SQL script
|
||||
let sqlScript = '-- MySQL database export\n\n';
|
||||
|
||||
// MySQL doesn't really use transactions for DDL statements but we'll add it for consistency
|
||||
sqlScript += 'START TRANSACTION;\n\n';
|
||||
|
||||
// Create databases (schemas) if they don't exist
|
||||
const schemas = new Set<string>();
|
||||
tables.forEach((table) => {
|
||||
if (table.schema) {
|
||||
schemas.add(table.schema);
|
||||
}
|
||||
});
|
||||
|
||||
schemas.forEach((schema) => {
|
||||
sqlScript += `CREATE DATABASE IF NOT EXISTS \`${schema}\`;\n`;
|
||||
});
|
||||
|
||||
if (schemas.size > 0) {
|
||||
sqlScript += '\n';
|
||||
}
|
||||
|
||||
// Generate table creation SQL
|
||||
sqlScript += tables
|
||||
.map((table: DBTable) => {
|
||||
// Skip views
|
||||
if (table.isView) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Use schema prefix if available
|
||||
const tableName = table.schema
|
||||
? `\`${table.schema}\`.\`${table.name}\``
|
||||
: `\`${table.name}\``;
|
||||
|
||||
// Get primary key fields
|
||||
const primaryKeyFields = table.fields.filter((f) => f.primaryKey);
|
||||
|
||||
return `${
|
||||
table.comments ? formatTableComment(table.comments) : ''
|
||||
}CREATE TABLE IF NOT EXISTS ${tableName} (\n${table.fields
|
||||
.map((field: DBField) => {
|
||||
const fieldName = `\`${field.name}\``;
|
||||
|
||||
// Handle type name - map to MySQL compatible types
|
||||
const typeName = mapMySQLType(field.type.name);
|
||||
|
||||
// Handle MySQL specific type formatting
|
||||
let typeWithSize = typeName;
|
||||
if (field.characterMaximumLength) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'varchar' ||
|
||||
typeName.toLowerCase() === 'char' ||
|
||||
typeName.toLowerCase() === 'varbinary'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.characterMaximumLength})`;
|
||||
}
|
||||
} else if (field.precision && field.scale) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'decimal' ||
|
||||
typeName.toLowerCase() === 'numeric'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.precision}, ${field.scale})`;
|
||||
}
|
||||
} else if (field.precision) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'decimal' ||
|
||||
typeName.toLowerCase() === 'numeric'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.precision})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Set a default size for VARCHAR columns if not specified
|
||||
if (
|
||||
typeName.toLowerCase() === 'varchar' &&
|
||||
!field.characterMaximumLength
|
||||
) {
|
||||
typeWithSize = `${typeName}(255)`;
|
||||
}
|
||||
|
||||
const notNull = field.nullable ? '' : ' NOT NULL';
|
||||
|
||||
// Handle auto_increment - MySQL uses AUTO_INCREMENT keyword
|
||||
let autoIncrement = '';
|
||||
if (
|
||||
field.primaryKey &&
|
||||
(field.default?.toLowerCase().includes('identity') ||
|
||||
field.default
|
||||
?.toLowerCase()
|
||||
.includes('autoincrement') ||
|
||||
field.default?.includes('nextval'))
|
||||
) {
|
||||
autoIncrement = ' AUTO_INCREMENT';
|
||||
}
|
||||
|
||||
// Only add UNIQUE constraint if the field is not part of the primary key
|
||||
const unique =
|
||||
!field.primaryKey && field.unique ? ' UNIQUE' : '';
|
||||
|
||||
// Handle default value
|
||||
const defaultValue =
|
||||
field.default &&
|
||||
!field.default.toLowerCase().includes('identity') &&
|
||||
!field.default
|
||||
.toLowerCase()
|
||||
.includes('autoincrement') &&
|
||||
!field.default.includes('nextval')
|
||||
? ` DEFAULT ${parseMySQLDefault(field)}`
|
||||
: '';
|
||||
|
||||
// MySQL supports inline comments
|
||||
const comment = field.comments
|
||||
? ` COMMENT '${escapeSQLComment(field.comments)}'`
|
||||
: '';
|
||||
|
||||
return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${typeWithSize}${notNull}${autoIncrement}${unique}${defaultValue}${comment}`;
|
||||
})
|
||||
.join(',\n')}${
|
||||
// Add PRIMARY KEY as table constraint
|
||||
primaryKeyFields.length > 0
|
||||
? `,\n PRIMARY KEY (${primaryKeyFields
|
||||
.map((f) => `\`${f.name}\``)
|
||||
.join(', ')})`
|
||||
: ''
|
||||
}\n)${
|
||||
// MySQL supports table comments
|
||||
table.comments
|
||||
? ` COMMENT='${escapeSQLComment(table.comments)}'`
|
||||
: ''
|
||||
};\n\n${
|
||||
// Add indexes - MySQL creates them separately from the table definition
|
||||
table.indexes
|
||||
.map((index) => {
|
||||
// Get the list of fields for this index
|
||||
const indexFields = index.fieldIds
|
||||
.map((fieldId) => {
|
||||
const field = table.fields.find(
|
||||
(f) => f.id === fieldId
|
||||
);
|
||||
return field ? field : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
// Skip if this index exactly matches the primary key fields
|
||||
if (
|
||||
primaryKeyFields.length === indexFields.length &&
|
||||
primaryKeyFields.every((pk) =>
|
||||
indexFields.some(
|
||||
(field) => field && field.id === pk.id
|
||||
)
|
||||
)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Create a unique index name by combining table name, field names, and a unique/non-unique indicator
|
||||
const fieldNamesForIndex = indexFields
|
||||
.map((field) => field?.name || '')
|
||||
.join('_');
|
||||
const uniqueIndicator = index.unique ? '_unique' : '';
|
||||
const indexName = `\`idx_${table.name}_${fieldNamesForIndex}${uniqueIndicator}\``;
|
||||
|
||||
// Get the properly quoted field names
|
||||
const indexFieldNames = indexFields
|
||||
.map((field) => (field ? `\`${field.name}\`` : ''))
|
||||
.filter(Boolean);
|
||||
|
||||
// Check for text/blob fields that need special handling
|
||||
const hasTextOrBlob = indexFields.some((field) => {
|
||||
const typeName =
|
||||
field?.type.name.toLowerCase() || '';
|
||||
return (
|
||||
typeName === 'text' ||
|
||||
typeName === 'mediumtext' ||
|
||||
typeName === 'longtext' ||
|
||||
typeName === 'blob'
|
||||
);
|
||||
});
|
||||
|
||||
// If there are TEXT/BLOB fields, need to add prefix length
|
||||
const indexFieldsWithPrefix = hasTextOrBlob
|
||||
? indexFieldNames.map((name) => {
|
||||
const field = indexFields.find(
|
||||
(f) => `\`${f?.name}\`` === name
|
||||
);
|
||||
if (!field) return name;
|
||||
|
||||
const typeName =
|
||||
field.type.name.toLowerCase();
|
||||
if (
|
||||
typeName === 'text' ||
|
||||
typeName === 'mediumtext' ||
|
||||
typeName === 'longtext' ||
|
||||
typeName === 'blob'
|
||||
) {
|
||||
// Add a prefix length for TEXT/BLOB fields (required in MySQL)
|
||||
return `${name}(255)`;
|
||||
}
|
||||
return name;
|
||||
})
|
||||
: indexFieldNames;
|
||||
|
||||
return indexFieldNames.length > 0
|
||||
? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName}\nON ${tableName} (${indexFieldsWithPrefix.join(', ')});\n`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}`;
|
||||
})
|
||||
.filter(Boolean) // Remove empty strings (views)
|
||||
.join('\n');
|
||||
|
||||
// Generate foreign keys
|
||||
if (relationships.length > 0) {
|
||||
sqlScript += '\n-- Foreign key constraints\n\n';
|
||||
|
||||
sqlScript += relationships
|
||||
.map((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
|
||||
) {
|
||||
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 '';
|
||||
}
|
||||
|
||||
const fkTableName = fkTable.schema
|
||||
? `\`${fkTable.schema}\`.\`${fkTable.name}\``
|
||||
: `\`${fkTable.name}\``;
|
||||
const refTableName = refTable.schema
|
||||
? `\`${refTable.schema}\`.\`${refTable.name}\``
|
||||
: `\`${refTable.name}\``;
|
||||
|
||||
// Create a descriptive constraint name
|
||||
const constraintName = `\`fk_${fkTable.name}_${fkField.name}\``;
|
||||
|
||||
// MySQL supports ON DELETE and ON UPDATE actions
|
||||
return `ALTER TABLE ${fkTableName}\nADD CONSTRAINT ${constraintName} FOREIGN KEY(\`${fkField.name}\`) REFERENCES ${refTableName}(\`${refField.name}\`)\nON UPDATE CASCADE ON DELETE RESTRICT;\n`;
|
||||
})
|
||||
.filter(Boolean) // Remove empty strings
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
sqlScript += '\nCOMMIT;\n';
|
||||
|
||||
return sqlScript;
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
import {
|
||||
exportFieldComment,
|
||||
escapeSQLComment,
|
||||
formatTableComment,
|
||||
isFunction,
|
||||
isKeyword,
|
||||
strHasQuotes,
|
||||
} from './common';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||
import { DBCustomTypeKind } from '@/lib/domain/db-custom-type';
|
||||
|
||||
function parsePostgresDefault(field: DBField): string {
|
||||
if (!field.default || typeof field.default !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const defaultValue = field.default.trim();
|
||||
|
||||
// Handle specific PostgreSQL default values
|
||||
if (defaultValue.toLowerCase() === 'now()') {
|
||||
return 'CURRENT_TIMESTAMP';
|
||||
}
|
||||
|
||||
// Handle PostgreSQL functions for JSON/JSONB types
|
||||
if (
|
||||
(field.type.name.toLowerCase() === 'json' ||
|
||||
field.type.name.toLowerCase() === 'jsonb') &&
|
||||
(defaultValue.includes('json_build_object') ||
|
||||
defaultValue.includes('jsonb_build_object') ||
|
||||
defaultValue.includes('json_build_array') ||
|
||||
defaultValue.includes('jsonb_build_array') ||
|
||||
defaultValue.includes('to_json') ||
|
||||
defaultValue.includes('to_jsonb'))
|
||||
) {
|
||||
// Remove any enclosing quotes and return the function call as is
|
||||
return defaultValue.replace(/^'(.*)'$/, '$1').replace(/''/, "'");
|
||||
}
|
||||
|
||||
// Handle nextval sequences for PostgreSQL
|
||||
if (defaultValue.includes('nextval')) {
|
||||
return defaultValue; // Keep it as is for PostgreSQL
|
||||
}
|
||||
|
||||
// If it's a function call, keep it as is
|
||||
if (isFunction(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it's a keyword, keep it as is
|
||||
if (isKeyword(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it already has quotes, keep it as is
|
||||
if (strHasQuotes(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it's a number, keep it as is
|
||||
if (/^-?\d+(\.\d+)?$/.test(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// For other cases, add quotes
|
||||
return `'${defaultValue.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
// Map problematic types to PostgreSQL compatible types
|
||||
function mapPostgresType(typeName: string, fieldName: string): string {
|
||||
typeName = typeName.toLowerCase();
|
||||
fieldName = fieldName.toLowerCase();
|
||||
|
||||
// Handle known problematic types
|
||||
if (typeName === 'user-defined') {
|
||||
return 'jsonb'; // Default fallback for user-defined types
|
||||
}
|
||||
|
||||
// Handle generic "array" type (when not specified as array of what)
|
||||
if (typeName === 'array') {
|
||||
return 'text[]'; // Default to text array
|
||||
}
|
||||
|
||||
// Handle array type notation
|
||||
if (typeName.endsWith('[]')) {
|
||||
const baseType = mapPostgresType(typeName.slice(0, -2), fieldName);
|
||||
return `${baseType}[]`;
|
||||
}
|
||||
|
||||
// Default case: return the type as is
|
||||
return typeName;
|
||||
}
|
||||
|
||||
function exportCustomTypes(customTypes: DBCustomType[]): string {
|
||||
if (!customTypes || customTypes.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let typesSql = '';
|
||||
|
||||
// Sort custom types to ensure enums are created before composite types that might use them
|
||||
const sortedTypes = [...customTypes].sort((a, b) => {
|
||||
if (
|
||||
a.kind === DBCustomTypeKind.enum &&
|
||||
b.kind === DBCustomTypeKind.composite
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
if (
|
||||
a.kind === DBCustomTypeKind.composite &&
|
||||
b.kind === DBCustomTypeKind.enum
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
sortedTypes.forEach((customType) => {
|
||||
const typeName = customType.schema
|
||||
? `"${customType.schema}"."${customType.name}"`
|
||||
: `"${customType.name}"`;
|
||||
|
||||
if (customType.kind === DBCustomTypeKind.enum) {
|
||||
// Export enum type
|
||||
if (customType.values && customType.values.length > 0) {
|
||||
const enumValues = customType.values
|
||||
.map((value) => `'${value.replace(/'/g, "''")}'`)
|
||||
.join(', ');
|
||||
typesSql += `CREATE TYPE ${typeName} AS ENUM (${enumValues});\n`;
|
||||
}
|
||||
} else if (customType.kind === DBCustomTypeKind.composite) {
|
||||
// Export composite type
|
||||
if (customType.fields && customType.fields.length > 0) {
|
||||
const compositeFields = customType.fields
|
||||
.map((field) => `"${field.field}" ${field.type}`)
|
||||
.join(', ');
|
||||
typesSql += `CREATE TYPE ${typeName} AS (${compositeFields});\n`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return typesSql + '\n';
|
||||
}
|
||||
|
||||
export function exportPostgreSQL(diagram: Diagram): string {
|
||||
if (!diagram.tables || !diagram.relationships) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tables = diagram.tables;
|
||||
const relationships = diagram.relationships;
|
||||
const customTypes = diagram.customTypes || [];
|
||||
|
||||
// Create CREATE SCHEMA statements for all schemas
|
||||
let sqlScript = '';
|
||||
const schemas = new Set<string>();
|
||||
|
||||
tables.forEach((table) => {
|
||||
if (table.schema) {
|
||||
schemas.add(table.schema);
|
||||
}
|
||||
});
|
||||
|
||||
// Also collect schemas from custom types
|
||||
customTypes.forEach((customType) => {
|
||||
if (customType.schema) {
|
||||
schemas.add(customType.schema);
|
||||
}
|
||||
});
|
||||
|
||||
// Add schema creation statements
|
||||
schemas.forEach((schema) => {
|
||||
sqlScript += `CREATE SCHEMA IF NOT EXISTS "${schema}";\n`;
|
||||
});
|
||||
sqlScript += '\n';
|
||||
|
||||
// Add custom types (enums and composite types)
|
||||
sqlScript += exportCustomTypes(customTypes);
|
||||
|
||||
// Add sequence creation statements
|
||||
const sequences = new Set<string>();
|
||||
|
||||
tables.forEach((table) => {
|
||||
table.fields.forEach((field) => {
|
||||
if (field.default) {
|
||||
// Match nextval('schema.sequence_name') or nextval('sequence_name')
|
||||
const match = field.default.match(
|
||||
/nextval\('([^']+)'(?:::[^)]+)?\)/
|
||||
);
|
||||
if (match) {
|
||||
sequences.add(match[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
sequences.forEach((sequence) => {
|
||||
sqlScript += `CREATE SEQUENCE IF NOT EXISTS ${sequence};\n`;
|
||||
});
|
||||
sqlScript += '\n';
|
||||
|
||||
// Generate table creation SQL
|
||||
sqlScript += tables
|
||||
.map((table: DBTable) => {
|
||||
// Skip views
|
||||
if (table.isView) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tableName = table.schema
|
||||
? `"${table.schema}"."${table.name}"`
|
||||
: `"${table.name}"`;
|
||||
|
||||
// Get primary key fields
|
||||
const primaryKeyFields = table.fields.filter((f) => f.primaryKey);
|
||||
|
||||
return `${
|
||||
table.comments ? formatTableComment(table.comments) : ''
|
||||
}CREATE TABLE ${tableName} (\n${table.fields
|
||||
.map((field: DBField) => {
|
||||
const fieldName = `"${field.name}"`;
|
||||
|
||||
// Handle type name - map problematic types to PostgreSQL compatible types
|
||||
const typeName = mapPostgresType(
|
||||
field.type.name,
|
||||
field.name
|
||||
);
|
||||
|
||||
// Handle PostgreSQL specific type formatting
|
||||
let typeWithSize = typeName;
|
||||
let serialType = null;
|
||||
|
||||
if (field.increment && !field.nullable) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'integer' ||
|
||||
typeName.toLowerCase() === 'int'
|
||||
) {
|
||||
serialType = 'SERIAL';
|
||||
} else if (typeName.toLowerCase() === 'bigint') {
|
||||
serialType = 'BIGSERIAL';
|
||||
} else if (typeName.toLowerCase() === 'smallint') {
|
||||
serialType = 'SMALLSERIAL';
|
||||
}
|
||||
}
|
||||
|
||||
if (field.characterMaximumLength) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'varchar' ||
|
||||
typeName.toLowerCase() === 'character varying' ||
|
||||
typeName.toLowerCase() === 'char' ||
|
||||
typeName.toLowerCase() === 'character'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.characterMaximumLength})`;
|
||||
}
|
||||
} else if (field.precision && field.scale) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'decimal' ||
|
||||
typeName.toLowerCase() === 'numeric'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.precision}, ${field.scale})`;
|
||||
}
|
||||
} else if (field.precision) {
|
||||
if (
|
||||
typeName.toLowerCase() === 'decimal' ||
|
||||
typeName.toLowerCase() === 'numeric'
|
||||
) {
|
||||
typeWithSize = `${typeName}(${field.precision})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle array types (check if the type name ends with '[]')
|
||||
if (typeName.endsWith('[]')) {
|
||||
typeWithSize = typeWithSize.replace('[]', '') + '[]';
|
||||
}
|
||||
|
||||
const notNull = field.nullable ? '' : ' NOT NULL';
|
||||
|
||||
// Handle identity generation
|
||||
let identity = '';
|
||||
if (field.default && field.default.includes('nextval')) {
|
||||
// PostgreSQL already handles this with DEFAULT nextval()
|
||||
} else if (
|
||||
field.default &&
|
||||
field.default.toLowerCase().includes('identity')
|
||||
) {
|
||||
identity = ' GENERATED BY DEFAULT AS IDENTITY';
|
||||
}
|
||||
|
||||
// Only add UNIQUE constraint if the field is not part of the primary key
|
||||
// This avoids redundant uniqueness constraints
|
||||
const unique =
|
||||
!field.primaryKey && field.unique ? ' UNIQUE' : '';
|
||||
|
||||
// Handle default value using PostgreSQL specific parser
|
||||
const defaultValue =
|
||||
field.default &&
|
||||
!field.default.toLowerCase().includes('identity')
|
||||
? ` DEFAULT ${parsePostgresDefault(field)}`
|
||||
: '';
|
||||
|
||||
// Do not add PRIMARY KEY as a column constraint - will add as table constraint
|
||||
return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${serialType || typeWithSize}${serialType ? '' : notNull}${identity}${unique}${defaultValue}`;
|
||||
})
|
||||
.join(',\n')}${
|
||||
primaryKeyFields.length > 0
|
||||
? `,\n PRIMARY KEY (${primaryKeyFields
|
||||
.map((f) => `"${f.name}"`)
|
||||
.join(', ')})`
|
||||
: ''
|
||||
}\n);\n\n${
|
||||
// Add table comments
|
||||
table.comments
|
||||
? `COMMENT ON TABLE ${tableName} IS '${escapeSQLComment(table.comments)}';\n\n`
|
||||
: ''
|
||||
}${
|
||||
// Add column comments
|
||||
table.fields
|
||||
.filter((f) => f.comments)
|
||||
.map(
|
||||
(f) =>
|
||||
`COMMENT ON COLUMN ${tableName}."${f.name}" IS '${escapeSQLComment(f.comments || '')}';\n`
|
||||
)
|
||||
.join('')
|
||||
}\n${
|
||||
// Add indexes only for non-primary key fields or composite indexes
|
||||
// This avoids duplicate indexes on primary key columns
|
||||
table.indexes
|
||||
.map((index) => {
|
||||
// Get the list of fields for this index
|
||||
const indexFields = index.fieldIds
|
||||
.map((fieldId) => {
|
||||
const field = table.fields.find(
|
||||
(f) => f.id === fieldId
|
||||
);
|
||||
return field ? field : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
// Skip if this index exactly matches the primary key fields
|
||||
// This prevents creating redundant indexes
|
||||
if (
|
||||
primaryKeyFields.length === indexFields.length &&
|
||||
primaryKeyFields.every((pk) =>
|
||||
indexFields.some(
|
||||
(field) => field && field.id === pk.id
|
||||
)
|
||||
)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Create unique index name using table name and index name
|
||||
// This ensures index names are unique across the database
|
||||
const safeTableName = table.name.replace(
|
||||
/[^a-zA-Z0-9_]/g,
|
||||
'_'
|
||||
);
|
||||
const safeIndexName = index.name.replace(
|
||||
/[^a-zA-Z0-9_]/g,
|
||||
'_'
|
||||
);
|
||||
|
||||
// Limit index name length to avoid PostgreSQL's 63-character identifier limit
|
||||
let combinedName = `${safeTableName}_${safeIndexName}`;
|
||||
if (combinedName.length > 60) {
|
||||
// If too long, use just the index name or a truncated version
|
||||
combinedName =
|
||||
safeIndexName.length > 60
|
||||
? safeIndexName.substring(0, 60)
|
||||
: safeIndexName;
|
||||
}
|
||||
|
||||
const indexName = `"${combinedName}"`;
|
||||
|
||||
// Get the properly quoted field names
|
||||
const indexFieldNames = indexFields
|
||||
.map((field) => (field ? `"${field.name}"` : ''))
|
||||
.filter(Boolean);
|
||||
|
||||
return indexFieldNames.length > 0
|
||||
? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName}\nON ${tableName} (${indexFieldNames.join(', ')});\n\n`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('')
|
||||
}`;
|
||||
})
|
||||
.filter(Boolean) // Remove empty strings (views)
|
||||
.join('\n');
|
||||
|
||||
// Generate foreign keys
|
||||
sqlScript += `\n${relationships
|
||||
.map((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
|
||||
) {
|
||||
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 '';
|
||||
}
|
||||
|
||||
const fkTableName = fkTable.schema
|
||||
? `"${fkTable.schema}"."${fkTable.name}"`
|
||||
: `"${fkTable.name}"`;
|
||||
const refTableName = refTable.schema
|
||||
? `"${refTable.schema}"."${refTable.name}"`
|
||||
: `"${refTable.name}"`;
|
||||
|
||||
// Create a unique constraint name by combining table and field names
|
||||
// Ensure it stays within PostgreSQL's 63-character limit for identifiers
|
||||
// and doesn't get truncated in a way that breaks SQL syntax
|
||||
const baseName = `fk_${fkTable.name}_${fkField.name}_${refTable.name}_${refField.name}`;
|
||||
// Limit to 60 chars (63 minus quotes) to ensure the whole identifier stays within limits
|
||||
const safeConstraintName =
|
||||
baseName.length > 60
|
||||
? baseName.substring(0, 60).replace(/[^a-zA-Z0-9_]/g, '_')
|
||||
: baseName.replace(/[^a-zA-Z0-9_]/g, '_');
|
||||
|
||||
const constraintName = `"${safeConstraintName}"`;
|
||||
|
||||
return `ALTER TABLE ${fkTableName}\nADD CONSTRAINT ${constraintName} FOREIGN KEY("${fkField.name}") REFERENCES ${refTableName}("${refField.name}");\n`;
|
||||
})
|
||||
.filter(Boolean) // Remove empty strings
|
||||
.join('\n')}`;
|
||||
|
||||
return sqlScript;
|
||||
}
|
||||
@@ -1,395 +0,0 @@
|
||||
import {
|
||||
exportFieldComment,
|
||||
formatTableComment,
|
||||
isFunction,
|
||||
isKeyword,
|
||||
strHasQuotes,
|
||||
} from './common';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
|
||||
function parseSQLiteDefault(field: DBField): string {
|
||||
if (!field.default) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const defaultValue = field.default.trim();
|
||||
|
||||
// Handle specific SQLite default values
|
||||
if (
|
||||
defaultValue.toLowerCase() === 'now()' ||
|
||||
defaultValue.toLowerCase() === 'current_timestamp'
|
||||
) {
|
||||
return 'CURRENT_TIMESTAMP';
|
||||
}
|
||||
|
||||
// Handle SQLite auto-increment
|
||||
if (
|
||||
defaultValue.toLowerCase().includes('identity') ||
|
||||
defaultValue.toLowerCase().includes('autoincrement') ||
|
||||
defaultValue.includes('nextval')
|
||||
) {
|
||||
return ''; // SQLite handles this differently with INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
}
|
||||
|
||||
// If it's a function call, convert to SQLite equivalents
|
||||
if (isFunction(defaultValue)) {
|
||||
// Map common PostgreSQL/MSSQL functions to SQLite equivalents
|
||||
if (
|
||||
defaultValue.toLowerCase().includes('newid()') ||
|
||||
defaultValue.toLowerCase().includes('uuid()')
|
||||
) {
|
||||
return 'lower(hex(randomblob(16)))';
|
||||
}
|
||||
|
||||
// For functions we can't translate, return as is (SQLite might not support them)
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it's a keyword, keep it as is
|
||||
if (isKeyword(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it already has quotes, keep it as is
|
||||
if (strHasQuotes(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If it's a number, keep it as is
|
||||
if (/^-?\d+(\.\d+)?$/.test(defaultValue)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// For other cases, add quotes
|
||||
return `'${defaultValue.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
// Map problematic types to SQLite compatible types
|
||||
function mapSQLiteType(typeName: string, isPrimaryKey: boolean): string {
|
||||
typeName = typeName.toLowerCase();
|
||||
|
||||
// Special handling for primary key integer columns (autoincrement requires INTEGER PRIMARY KEY)
|
||||
if (isPrimaryKey && (typeName === 'integer' || typeName === 'int')) {
|
||||
return 'INTEGER'; // Must be uppercase for SQLite to recognize it for AUTOINCREMENT
|
||||
}
|
||||
|
||||
// Map common types to SQLite's simplified type system
|
||||
switch (typeName) {
|
||||
case 'int':
|
||||
case 'smallint':
|
||||
case 'tinyint':
|
||||
case 'mediumint':
|
||||
case 'bigint':
|
||||
return 'INTEGER';
|
||||
|
||||
case 'decimal':
|
||||
case 'numeric':
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'real':
|
||||
return 'REAL';
|
||||
|
||||
case 'char':
|
||||
case 'nchar':
|
||||
case 'varchar':
|
||||
case 'nvarchar':
|
||||
case 'text':
|
||||
case 'ntext':
|
||||
case 'character varying':
|
||||
case 'character':
|
||||
return 'TEXT';
|
||||
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'timestamp':
|
||||
case 'datetime2':
|
||||
return 'TEXT'; // SQLite doesn't have dedicated date types
|
||||
|
||||
case 'blob':
|
||||
case 'binary':
|
||||
case 'varbinary':
|
||||
case 'image':
|
||||
return 'BLOB';
|
||||
|
||||
case 'bit':
|
||||
case 'boolean':
|
||||
return 'INTEGER'; // SQLite doesn't have a boolean type, use INTEGER
|
||||
|
||||
case 'user-defined':
|
||||
case 'json':
|
||||
case 'jsonb':
|
||||
return 'TEXT'; // Store as JSON text
|
||||
|
||||
case 'array':
|
||||
return 'TEXT'; // Store as serialized array text
|
||||
|
||||
case 'geometry':
|
||||
case 'geography':
|
||||
return 'BLOB'; // Store spatial data as BLOB in SQLite
|
||||
}
|
||||
|
||||
// If type has array notation (ends with []), treat as TEXT
|
||||
if (typeName.endsWith('[]')) {
|
||||
return 'TEXT';
|
||||
}
|
||||
|
||||
// For any other types, default to TEXT
|
||||
return typeName;
|
||||
}
|
||||
|
||||
export function exportSQLite(diagram: Diagram): string {
|
||||
if (!diagram.tables || !diagram.relationships) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tables = diagram.tables;
|
||||
const relationships = diagram.relationships;
|
||||
|
||||
// Start SQL script - SQLite doesn't use schemas, so we skip schema creation
|
||||
let sqlScript = '-- SQLite database export\n\n';
|
||||
|
||||
// Begin transaction for faster import
|
||||
sqlScript += 'BEGIN TRANSACTION;\n\n';
|
||||
|
||||
// SQLite doesn't have sequences, so we skip sequence creation
|
||||
|
||||
// SQLite system tables that should be skipped
|
||||
const sqliteSystemTables = [
|
||||
'sqlite_sequence',
|
||||
'sqlite_stat1',
|
||||
'sqlite_stat2',
|
||||
'sqlite_stat3',
|
||||
'sqlite_stat4',
|
||||
'sqlite_master',
|
||||
];
|
||||
|
||||
// Generate table creation SQL
|
||||
sqlScript += tables
|
||||
.map((table: DBTable) => {
|
||||
// Skip views
|
||||
if (table.isView) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Skip SQLite system tables
|
||||
if (sqliteSystemTables.includes(table.name.toLowerCase())) {
|
||||
return `-- Skipping SQLite system table: "${table.name}"\n`;
|
||||
}
|
||||
|
||||
// SQLite doesn't use schema prefixes, so we use just the table name
|
||||
// Include the schema in a comment if it exists
|
||||
const schemaComment = table.schema
|
||||
? `-- Original schema: ${table.schema}\n`
|
||||
: '';
|
||||
const tableName = `"${table.name}"`;
|
||||
|
||||
// Get primary key fields
|
||||
const primaryKeyFields = table.fields.filter((f) => f.primaryKey);
|
||||
|
||||
// Check if this is a single-column INTEGER PRIMARY KEY (for AUTOINCREMENT)
|
||||
const singleIntegerPrimaryKey =
|
||||
primaryKeyFields.length === 1 &&
|
||||
(primaryKeyFields[0].type.name.toLowerCase() === 'integer' ||
|
||||
primaryKeyFields[0].type.name.toLowerCase() === 'int');
|
||||
|
||||
return `${schemaComment}${
|
||||
table.comments ? formatTableComment(table.comments) : ''
|
||||
}CREATE TABLE IF NOT EXISTS ${tableName} (\n${table.fields
|
||||
.map((field: DBField) => {
|
||||
const fieldName = `"${field.name}"`;
|
||||
|
||||
// Handle type name - map to SQLite compatible types
|
||||
const typeName = mapSQLiteType(
|
||||
field.type.name,
|
||||
field.primaryKey
|
||||
);
|
||||
|
||||
// SQLite ignores length specifiers, so we don't add them
|
||||
// We'll keep this simple without size info
|
||||
const typeWithoutSize = typeName;
|
||||
|
||||
const notNull = field.nullable ? '' : ' NOT NULL';
|
||||
|
||||
// Handle autoincrement - only works with INTEGER PRIMARY KEY
|
||||
let autoIncrement = '';
|
||||
if (
|
||||
field.primaryKey &&
|
||||
singleIntegerPrimaryKey &&
|
||||
(field.default?.toLowerCase().includes('identity') ||
|
||||
field.default
|
||||
?.toLowerCase()
|
||||
.includes('autoincrement') ||
|
||||
field.default?.includes('nextval'))
|
||||
) {
|
||||
autoIncrement = ' AUTOINCREMENT';
|
||||
}
|
||||
|
||||
// Only add UNIQUE constraint if the field is not part of the primary key
|
||||
const unique =
|
||||
!field.primaryKey && field.unique ? ' UNIQUE' : '';
|
||||
|
||||
// Handle default value - Special handling for datetime() function
|
||||
let defaultValue = '';
|
||||
if (
|
||||
field.default &&
|
||||
!field.default.toLowerCase().includes('identity') &&
|
||||
!field.default
|
||||
.toLowerCase()
|
||||
.includes('autoincrement') &&
|
||||
!field.default.includes('nextval')
|
||||
) {
|
||||
// Special handling for quoted functions like 'datetime(\'\'now\'\')' - remove extra quotes
|
||||
if (field.default.includes("datetime(''now'')")) {
|
||||
defaultValue = ' DEFAULT CURRENT_TIMESTAMP';
|
||||
} else {
|
||||
defaultValue = ` DEFAULT ${parseSQLiteDefault(field)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add PRIMARY KEY inline only for single INTEGER primary key
|
||||
const primaryKey =
|
||||
field.primaryKey && singleIntegerPrimaryKey
|
||||
? ' PRIMARY KEY' + autoIncrement
|
||||
: '';
|
||||
|
||||
return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${typeWithoutSize}${primaryKey}${notNull}${unique}${defaultValue}`;
|
||||
})
|
||||
.join(',\n')}${
|
||||
// Add PRIMARY KEY as table constraint for composite primary keys or non-INTEGER primary keys
|
||||
primaryKeyFields.length > 0 && !singleIntegerPrimaryKey
|
||||
? `,\n PRIMARY KEY (${primaryKeyFields
|
||||
.map((f) => `"${f.name}"`)
|
||||
.join(', ')})`
|
||||
: ''
|
||||
}\n);\n\n${
|
||||
// Add indexes - SQLite doesn't support indexes in CREATE TABLE
|
||||
table.indexes
|
||||
.map((index) => {
|
||||
// Skip indexes that exactly match the primary key
|
||||
const indexFields = index.fieldIds
|
||||
.map((fieldId) => {
|
||||
const field = table.fields.find(
|
||||
(f) => f.id === fieldId
|
||||
);
|
||||
return field ? field : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
// Get the properly quoted field names
|
||||
const indexFieldNames = indexFields
|
||||
.map((field) => (field ? `"${field.name}"` : ''))
|
||||
.filter(Boolean);
|
||||
|
||||
// Skip if this index exactly matches the primary key fields
|
||||
if (
|
||||
primaryKeyFields.length === indexFields.length &&
|
||||
primaryKeyFields.every((pk) =>
|
||||
indexFields.some(
|
||||
(field) => field && field.id === pk.id
|
||||
)
|
||||
)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Create safe index name
|
||||
const safeIndexName = `${table.name}_${index.name}`
|
||||
.replace(/[^a-zA-Z0-9_]/g, '_')
|
||||
.substring(0, 60);
|
||||
|
||||
return indexFieldNames.length > 0
|
||||
? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX IF NOT EXISTS "${safeIndexName}"\nON ${tableName} (${indexFieldNames.join(', ')});\n`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}`;
|
||||
})
|
||||
.filter(Boolean) // Remove empty strings (views)
|
||||
.join('\n');
|
||||
|
||||
// Generate table constraints and triggers for foreign keys
|
||||
// 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\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
|
||||
sqlScript += '\nCOMMIT;\n';
|
||||
|
||||
return sqlScript;
|
||||
}
|
||||
21
src/lib/data/import-metadata/import/custom-types.ts
Normal file
21
src/lib/data/import-metadata/import/custom-types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { DBCustomType, DBCustomTypeKind } from '@/lib/domain';
|
||||
import { schemaNameToDomainSchemaName } from '@/lib/domain';
|
||||
import type { DBCustomTypeInfo } from '../metadata-types/custom-type-info';
|
||||
import { generateId } from '@/lib/utils';
|
||||
|
||||
export const createCustomTypesFromMetadata = ({
|
||||
customTypes,
|
||||
}: {
|
||||
customTypes: DBCustomTypeInfo[];
|
||||
}): DBCustomType[] => {
|
||||
return customTypes.map((customType) => {
|
||||
return {
|
||||
id: generateId(),
|
||||
schema: schemaNameToDomainSchemaName(customType.schema),
|
||||
name: customType.type,
|
||||
kind: customType.kind as DBCustomTypeKind,
|
||||
values: customType.values,
|
||||
fields: customType.fields,
|
||||
};
|
||||
});
|
||||
};
|
||||
351
src/lib/data/import-metadata/import/dependencies.ts
Normal file
351
src/lib/data/import-metadata/import/dependencies.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
import { generateId } from '@/lib/utils';
|
||||
import type { AST } from 'node-sql-parser';
|
||||
import type { DBDependency, DBTable } from '@/lib/domain';
|
||||
import { DatabaseType, schemaNameToDomainSchemaName } from '@/lib/domain';
|
||||
import type { ViewInfo } from '../metadata-types/view-info';
|
||||
import { decodeViewDefinition } from './tables';
|
||||
|
||||
const astDatabaseTypes: Record<DatabaseType, string> = {
|
||||
[DatabaseType.POSTGRESQL]: 'postgresql',
|
||||
[DatabaseType.MYSQL]: 'postgresql',
|
||||
[DatabaseType.MARIADB]: 'postgresql',
|
||||
[DatabaseType.GENERIC]: 'postgresql',
|
||||
[DatabaseType.SQLITE]: 'postgresql',
|
||||
[DatabaseType.SQL_SERVER]: 'postgresql',
|
||||
[DatabaseType.CLICKHOUSE]: 'postgresql',
|
||||
[DatabaseType.COCKROACHDB]: 'postgresql',
|
||||
[DatabaseType.ORACLE]: 'postgresql',
|
||||
};
|
||||
|
||||
export const createDependenciesFromMetadata = async ({
|
||||
views,
|
||||
tables,
|
||||
databaseType,
|
||||
}: {
|
||||
views: ViewInfo[];
|
||||
tables: DBTable[];
|
||||
databaseType: DatabaseType;
|
||||
}): Promise<DBDependency[]> => {
|
||||
if (!views || views.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { Parser } = await import('node-sql-parser');
|
||||
const parser = new Parser();
|
||||
|
||||
const dependencies = views
|
||||
.flatMap((view) => {
|
||||
const viewSchema = schemaNameToDomainSchemaName(view.schema);
|
||||
const viewTable = tables.find(
|
||||
(table) =>
|
||||
table.name === view.view_name && viewSchema === table.schema
|
||||
);
|
||||
|
||||
if (!viewTable) {
|
||||
console.warn(
|
||||
`Source table for view ${view.view_name} not found (schema: ${viewSchema})`
|
||||
);
|
||||
return []; // Skip this view and proceed to the next
|
||||
}
|
||||
|
||||
if (view.view_definition) {
|
||||
try {
|
||||
const decodedViewDefinition = decodeViewDefinition(
|
||||
databaseType,
|
||||
view.view_definition
|
||||
);
|
||||
|
||||
let modifiedViewDefinition = '';
|
||||
if (
|
||||
databaseType === DatabaseType.MYSQL ||
|
||||
databaseType === DatabaseType.MARIADB
|
||||
) {
|
||||
modifiedViewDefinition = preprocessViewDefinitionMySQL(
|
||||
decodedViewDefinition
|
||||
);
|
||||
} else if (databaseType === DatabaseType.SQL_SERVER) {
|
||||
modifiedViewDefinition =
|
||||
preprocessViewDefinitionSQLServer(
|
||||
decodedViewDefinition
|
||||
);
|
||||
} else {
|
||||
modifiedViewDefinition = preprocessViewDefinition(
|
||||
decodedViewDefinition
|
||||
);
|
||||
}
|
||||
|
||||
// Parse using the appropriate dialect
|
||||
const ast = parser.astify(modifiedViewDefinition, {
|
||||
database: astDatabaseTypes[databaseType],
|
||||
type: 'select', // Parsing a SELECT statement
|
||||
});
|
||||
|
||||
let relatedTables = extractTablesFromAST(ast);
|
||||
|
||||
// Filter out duplicate tables without schema
|
||||
relatedTables = filterDuplicateTables(relatedTables);
|
||||
|
||||
return relatedTables.map((relTable) => {
|
||||
const relSchema = relTable.schema || view.schema; // Use view's schema if relSchema is undefined
|
||||
const relTableName = relTable.tableName;
|
||||
|
||||
const table = tables.find(
|
||||
(table) =>
|
||||
table.name === relTableName &&
|
||||
(table.schema || '') === relSchema
|
||||
);
|
||||
|
||||
if (table) {
|
||||
const dependency: DBDependency = {
|
||||
id: generateId(),
|
||||
schema: view.schema,
|
||||
tableId: table.id, // related table
|
||||
dependentSchema: table.schema,
|
||||
dependentTableId: viewTable.id, // dependent view
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
return dependency;
|
||||
} else {
|
||||
console.warn(
|
||||
`Dependent table ${relSchema}.${relTableName} not found for view ${view.schema}.${view.view_name}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error parsing view ${view.schema}.${view.view_name}:`,
|
||||
error
|
||||
);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`View definition missing for ${view.schema}.${view.view_name}`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.filter((dependency) => dependency !== null);
|
||||
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
// Add this new function to filter out duplicate tables
|
||||
function filterDuplicateTables(
|
||||
tables: { schema?: string; tableName: string }[]
|
||||
): { schema?: string; tableName: string }[] {
|
||||
const tableMap = new Map<string, { schema?: string; tableName: string }>();
|
||||
|
||||
for (const table of tables) {
|
||||
const key = table.tableName;
|
||||
const existingTable = tableMap.get(key);
|
||||
|
||||
if (!existingTable || (table.schema && !existingTable.schema)) {
|
||||
tableMap.set(key, table);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(tableMap.values());
|
||||
}
|
||||
|
||||
// Preprocess the view_definition to remove schema from CREATE VIEW
|
||||
function preprocessViewDefinition(viewDefinition: string): string {
|
||||
if (!viewDefinition) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Remove leading and trailing whitespace
|
||||
viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Replace escaped double quotes with regular ones
|
||||
viewDefinition = viewDefinition.replace(/\\"/g, '"');
|
||||
|
||||
// Replace 'CREATE MATERIALIZED VIEW' with 'CREATE VIEW'
|
||||
viewDefinition = viewDefinition.replace(
|
||||
/CREATE\s+MATERIALIZED\s+VIEW/i,
|
||||
'CREATE VIEW'
|
||||
);
|
||||
|
||||
// Regular expression to match 'CREATE VIEW [schema.]view_name [ (column definitions) ] AS'
|
||||
// This regex captures the view name and skips any content between the view name and 'AS'
|
||||
const regex =
|
||||
/CREATE\s+VIEW\s+(?:(?:`[^`]+`|"[^"]+"|\w+)\.)?(?:`([^`]+)`|"([^"]+)"|(\w+))[\s\S]*?\bAS\b\s+/i;
|
||||
|
||||
const match = viewDefinition.match(regex);
|
||||
let modifiedDefinition: string;
|
||||
|
||||
if (match) {
|
||||
const viewName = match[1] || match[2] || match[3];
|
||||
// Extract the SQL after the 'AS' keyword
|
||||
const restOfDefinition = viewDefinition.substring(
|
||||
match.index! + match[0].length
|
||||
);
|
||||
|
||||
// Replace double-quoted identifiers with unquoted ones
|
||||
let modifiedSQL = restOfDefinition.replace(/"(\w+)"/g, '$1');
|
||||
|
||||
// Replace '::' type casts with 'CAST' expressions
|
||||
modifiedSQL = modifiedSQL.replace(
|
||||
/\(([^()]+)\)::(\w+)/g,
|
||||
'CAST($1 AS $2)'
|
||||
);
|
||||
|
||||
// Remove ClickHouse-specific syntax that may still be present
|
||||
// For example, remove SETTINGS clauses inside the SELECT statement
|
||||
modifiedSQL = modifiedSQL.replace(/\bSETTINGS\b[\s\S]*$/i, '');
|
||||
|
||||
modifiedDefinition = `CREATE VIEW ${viewName} AS ${modifiedSQL}`;
|
||||
} else {
|
||||
console.warn('Could not preprocess view definition:', viewDefinition);
|
||||
modifiedDefinition = viewDefinition;
|
||||
}
|
||||
|
||||
return modifiedDefinition;
|
||||
}
|
||||
|
||||
// Preprocess the view_definition for SQL Server
|
||||
function preprocessViewDefinitionSQLServer(viewDefinition: string): string {
|
||||
if (!viewDefinition) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Remove BOM if present
|
||||
viewDefinition = viewDefinition.replace(/^\uFEFF/, '');
|
||||
|
||||
// Normalize whitespace
|
||||
viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Remove square brackets and replace with double quotes
|
||||
viewDefinition = viewDefinition.replace(/\[([^\]]+)\]/g, '"$1"');
|
||||
|
||||
// Remove database names from fully qualified identifiers
|
||||
viewDefinition = viewDefinition.replace(
|
||||
/"([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"/g,
|
||||
'"$2"."$3"'
|
||||
);
|
||||
|
||||
// Replace SQL Server functions with PostgreSQL equivalents
|
||||
viewDefinition = viewDefinition.replace(/\bGETDATE\(\)/gi, 'NOW()');
|
||||
viewDefinition = viewDefinition.replace(/\bISNULL\(/gi, 'COALESCE(');
|
||||
|
||||
// Replace 'TOP N' with 'LIMIT N' at the end of the query
|
||||
const topMatch = viewDefinition.match(/SELECT\s+TOP\s+(\d+)/i);
|
||||
if (topMatch) {
|
||||
const topN = topMatch[1];
|
||||
viewDefinition = viewDefinition.replace(
|
||||
/SELECT\s+TOP\s+\d+/i,
|
||||
'SELECT'
|
||||
);
|
||||
viewDefinition = viewDefinition.replace(/;+\s*$/, ''); // Remove semicolons at the end
|
||||
viewDefinition += ` LIMIT ${topN}`;
|
||||
}
|
||||
|
||||
viewDefinition = viewDefinition.replace(/\n/g, ''); // Remove newlines
|
||||
|
||||
// Adjust CREATE VIEW syntax
|
||||
const regex =
|
||||
/CREATE\s+VIEW\s+(?:"?([^".\s]+)"?\.)?"?([^".\s]+)"?\s+AS\s+/i;
|
||||
const match = viewDefinition.match(regex);
|
||||
let modifiedDefinition: string;
|
||||
|
||||
if (match) {
|
||||
const viewName = match[2];
|
||||
const modifiedSQL = viewDefinition.substring(
|
||||
match.index! + match[0].length
|
||||
);
|
||||
|
||||
// Remove semicolons at the end
|
||||
const finalSQL = modifiedSQL.replace(/;+\s*$/, '');
|
||||
|
||||
modifiedDefinition = `CREATE VIEW "${viewName}" AS ${finalSQL}`;
|
||||
} else {
|
||||
console.warn('Could not preprocess view definition:', viewDefinition);
|
||||
modifiedDefinition = viewDefinition;
|
||||
}
|
||||
|
||||
return modifiedDefinition;
|
||||
}
|
||||
|
||||
// Preprocess the view_definition to remove schema from CREATE VIEW
|
||||
function preprocessViewDefinitionMySQL(viewDefinition: string): string {
|
||||
if (!viewDefinition) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Remove any trailing semicolons
|
||||
viewDefinition = viewDefinition.replace(/;\s*$/, '');
|
||||
|
||||
// Remove backticks from identifiers
|
||||
viewDefinition = viewDefinition.replace(/`/g, '');
|
||||
|
||||
// Remove unnecessary parentheses around joins and ON clauses
|
||||
viewDefinition = removeRedundantParentheses(viewDefinition);
|
||||
|
||||
return viewDefinition;
|
||||
}
|
||||
|
||||
function removeRedundantParentheses(sql: string): string {
|
||||
// Regular expressions to match unnecessary parentheses
|
||||
const patterns = [
|
||||
/\(\s*(JOIN\s+[^()]+?)\s*\)/gi,
|
||||
/\(\s*(ON\s+[^()]+?)\s*\)/gi,
|
||||
// Additional patterns if necessary
|
||||
];
|
||||
|
||||
let prevSql;
|
||||
do {
|
||||
prevSql = sql;
|
||||
patterns.forEach((pattern) => {
|
||||
sql = sql.replace(pattern, '$1');
|
||||
});
|
||||
} while (sql !== prevSql);
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
function extractTablesFromAST(
|
||||
ast: AST | AST[]
|
||||
): { schema?: string; tableName: string }[] {
|
||||
const tablesMap = new Map<string, { schema: string; tableName: string }>();
|
||||
const visitedNodes = new Set();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function traverse(node: any) {
|
||||
if (!node || visitedNodes.has(node)) return;
|
||||
visitedNodes.add(node);
|
||||
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(traverse);
|
||||
} else if (typeof node === 'object') {
|
||||
// Check if node represents a table
|
||||
if (
|
||||
Object.hasOwnProperty.call(node, 'table') &&
|
||||
typeof node.table === 'string'
|
||||
) {
|
||||
let schema = node.db || node.schema;
|
||||
const tableName = node.table;
|
||||
if (tableName) {
|
||||
// Assign default schema if undefined
|
||||
schema = schemaNameToDomainSchemaName(schema) || '';
|
||||
const key = `${schema}.${tableName}`;
|
||||
if (!tablesMap.has(key)) {
|
||||
tablesMap.set(key, { schema, tableName });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively traverse all properties
|
||||
for (const key in node) {
|
||||
if (Object.hasOwnProperty.call(node, key)) {
|
||||
traverse(node[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse(ast);
|
||||
|
||||
return Array.from(tablesMap.values());
|
||||
}
|
||||
64
src/lib/data/import-metadata/import/fields.ts
Normal file
64
src/lib/data/import-metadata/import/fields.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { DBField } from '@/lib/domain';
|
||||
import type { ColumnInfo } from '../metadata-types/column-info';
|
||||
import type { AggregatedIndexInfo } from '../metadata-types/index-info';
|
||||
import type { PrimaryKeyInfo } from '../metadata-types/primary-key-info';
|
||||
import type { TableInfo } from '../metadata-types/table-info';
|
||||
import { generateId } from '@/lib/utils';
|
||||
|
||||
export const createFieldsFromMetadata = ({
|
||||
tableColumns,
|
||||
tablePrimaryKeys,
|
||||
aggregatedIndexes,
|
||||
}: {
|
||||
tableColumns: ColumnInfo[];
|
||||
tableSchema?: string;
|
||||
tableInfo: TableInfo;
|
||||
tablePrimaryKeys: PrimaryKeyInfo[];
|
||||
aggregatedIndexes: AggregatedIndexInfo[];
|
||||
}) => {
|
||||
const uniqueColumns = tableColumns.reduce((acc, col) => {
|
||||
if (!acc.has(col.name)) {
|
||||
acc.set(col.name, col);
|
||||
}
|
||||
return acc;
|
||||
}, new Map<string, ColumnInfo>());
|
||||
|
||||
const sortedColumns = Array.from(uniqueColumns.values()).sort(
|
||||
(a, b) => a.ordinal_position - b.ordinal_position
|
||||
);
|
||||
|
||||
const tablePrimaryKeysColumns = tablePrimaryKeys.map((pk) =>
|
||||
pk.column.trim()
|
||||
);
|
||||
|
||||
return sortedColumns.map(
|
||||
(col: ColumnInfo): DBField => ({
|
||||
id: generateId(),
|
||||
name: col.name,
|
||||
type: {
|
||||
id: col.type.split(' ').join('_').toLowerCase(),
|
||||
name: col.type.toLowerCase(),
|
||||
},
|
||||
primaryKey: tablePrimaryKeysColumns.includes(col.name),
|
||||
unique: Object.values(aggregatedIndexes).some(
|
||||
(idx) =>
|
||||
idx.unique &&
|
||||
idx.columns.length === 1 &&
|
||||
idx.columns[0].name === col.name
|
||||
),
|
||||
nullable: Boolean(col.nullable),
|
||||
...(col.character_maximum_length &&
|
||||
col.character_maximum_length !== 'null'
|
||||
? { characterMaximumLength: col.character_maximum_length }
|
||||
: {}),
|
||||
...(col.precision?.precision
|
||||
? { precision: col.precision.precision }
|
||||
: {}),
|
||||
...(col.precision?.scale ? { scale: col.precision.scale } : {}),
|
||||
...(col.default ? { default: col.default } : {}),
|
||||
...(col.collation ? { collation: col.collation } : {}),
|
||||
createdAt: Date.now(),
|
||||
comments: col.comment ? col.comment : undefined,
|
||||
})
|
||||
);
|
||||
};
|
||||
82
src/lib/data/import-metadata/import/index.ts
Normal file
82
src/lib/data/import-metadata/import/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { DatabaseEdition, Diagram } from '@/lib/domain';
|
||||
import { adjustTablePositions, DatabaseType } from '@/lib/domain';
|
||||
import { generateDiagramId } from '@/lib/utils';
|
||||
import type { DatabaseMetadata } from '../metadata-types/database-metadata';
|
||||
import { createCustomTypesFromMetadata } from './custom-types';
|
||||
import { createRelationshipsFromMetadata } from './relationships';
|
||||
import { createTablesFromMetadata } from './tables';
|
||||
import { createDependenciesFromMetadata } from './dependencies';
|
||||
|
||||
export const loadFromDatabaseMetadata = async ({
|
||||
databaseType,
|
||||
databaseMetadata,
|
||||
diagramNumber,
|
||||
databaseEdition,
|
||||
}: {
|
||||
databaseType: DatabaseType;
|
||||
databaseMetadata: DatabaseMetadata;
|
||||
diagramNumber?: number;
|
||||
databaseEdition?: DatabaseEdition;
|
||||
}): Promise<Diagram> => {
|
||||
const {
|
||||
fk_info: foreignKeys,
|
||||
views: views,
|
||||
custom_types: customTypes,
|
||||
} = databaseMetadata;
|
||||
|
||||
const tables = createTablesFromMetadata({
|
||||
databaseMetadata,
|
||||
databaseType,
|
||||
});
|
||||
|
||||
const relationships = createRelationshipsFromMetadata({
|
||||
foreignKeys,
|
||||
tables,
|
||||
});
|
||||
|
||||
const dependencies = await createDependenciesFromMetadata({
|
||||
views,
|
||||
tables,
|
||||
databaseType,
|
||||
});
|
||||
|
||||
const dbCustomTypes = customTypes
|
||||
? createCustomTypesFromMetadata({
|
||||
customTypes,
|
||||
})
|
||||
: [];
|
||||
|
||||
const adjustedTables = adjustTablePositions({
|
||||
tables,
|
||||
relationships,
|
||||
mode: 'perSchema',
|
||||
});
|
||||
|
||||
const sortedTables = adjustedTables.sort((a, b) => {
|
||||
if (a.isView === b.isView) {
|
||||
// Both are either tables or views, so sort alphabetically by name
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
// If one is a view and the other is not, put tables first
|
||||
return a.isView ? 1 : -1;
|
||||
});
|
||||
|
||||
const diagram: Diagram = {
|
||||
id: generateDiagramId(),
|
||||
name: databaseMetadata.database_name
|
||||
? `${databaseMetadata.database_name}-db`
|
||||
: diagramNumber
|
||||
? `Diagram ${diagramNumber}`
|
||||
: 'New Diagram',
|
||||
databaseType: databaseType ?? DatabaseType.GENERIC,
|
||||
databaseEdition,
|
||||
tables: sortedTables,
|
||||
relationships,
|
||||
dependencies,
|
||||
customTypes: dbCustomTypes,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
return diagram;
|
||||
};
|
||||
24
src/lib/data/import-metadata/import/indexes.ts
Normal file
24
src/lib/data/import-metadata/import/indexes.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { DBField, DBIndex, IndexType } from '@/lib/domain';
|
||||
import type { AggregatedIndexInfo } from '../metadata-types/index-info';
|
||||
import { generateId } from '@/lib/utils';
|
||||
|
||||
export const createIndexesFromMetadata = ({
|
||||
aggregatedIndexes,
|
||||
fields,
|
||||
}: {
|
||||
aggregatedIndexes: AggregatedIndexInfo[];
|
||||
fields: DBField[];
|
||||
}): DBIndex[] =>
|
||||
aggregatedIndexes.map(
|
||||
(idx): DBIndex => ({
|
||||
id: generateId(),
|
||||
name: idx.name,
|
||||
unique: Boolean(idx.unique),
|
||||
fieldIds: idx.columns
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map((c) => fields.find((f) => f.name === c.name)?.id)
|
||||
.filter((id): id is string => id !== undefined),
|
||||
createdAt: Date.now(),
|
||||
type: idx.index_type?.toLowerCase() as IndexType,
|
||||
})
|
||||
);
|
||||
85
src/lib/data/import-metadata/import/relationships.ts
Normal file
85
src/lib/data/import-metadata/import/relationships.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type {
|
||||
Cardinality,
|
||||
DBField,
|
||||
DBRelationship,
|
||||
DBTable,
|
||||
} from '@/lib/domain';
|
||||
import { schemaNameToDomainSchemaName } from '@/lib/domain';
|
||||
import type { ForeignKeyInfo } from '../metadata-types/foreign-key-info';
|
||||
import { generateId } from '@/lib/utils';
|
||||
|
||||
const determineCardinality = (
|
||||
field: DBField,
|
||||
isTablePKComplex: boolean
|
||||
): Cardinality => {
|
||||
return field.unique || (field.primaryKey && !isTablePKComplex)
|
||||
? 'one'
|
||||
: 'many';
|
||||
};
|
||||
|
||||
export const createRelationshipsFromMetadata = ({
|
||||
foreignKeys,
|
||||
tables,
|
||||
}: {
|
||||
foreignKeys: ForeignKeyInfo[];
|
||||
tables: DBTable[];
|
||||
}): DBRelationship[] => {
|
||||
return foreignKeys
|
||||
.map((fk: ForeignKeyInfo): DBRelationship | null => {
|
||||
const schema = schemaNameToDomainSchemaName(fk.schema);
|
||||
const sourceTable = tables.find(
|
||||
(table) => table.name === fk.table && table.schema === schema
|
||||
);
|
||||
|
||||
const targetSchema = schemaNameToDomainSchemaName(
|
||||
fk.reference_schema
|
||||
);
|
||||
|
||||
const targetTable = tables.find(
|
||||
(table) =>
|
||||
table.name === fk.reference_table &&
|
||||
table.schema === targetSchema
|
||||
);
|
||||
const sourceField = sourceTable?.fields.find(
|
||||
(field) => field.name === fk.column
|
||||
);
|
||||
const targetField = targetTable?.fields.find(
|
||||
(field) => field.name === fk.reference_column
|
||||
);
|
||||
|
||||
const isSourceTablePKComplex =
|
||||
(sourceTable?.fields.filter((field) => field.primaryKey) ?? [])
|
||||
.length > 1;
|
||||
const isTargetTablePKComplex =
|
||||
(targetTable?.fields.filter((field) => field.primaryKey) ?? [])
|
||||
.length > 1;
|
||||
|
||||
if (sourceTable && targetTable && sourceField && targetField) {
|
||||
const sourceCardinality = determineCardinality(
|
||||
sourceField,
|
||||
isSourceTablePKComplex
|
||||
);
|
||||
const targetCardinality = determineCardinality(
|
||||
targetField,
|
||||
isTargetTablePKComplex
|
||||
);
|
||||
|
||||
return {
|
||||
id: generateId(),
|
||||
name: fk.foreign_key_name,
|
||||
sourceSchema: schema,
|
||||
targetSchema: targetSchema,
|
||||
sourceTableId: sourceTable.id,
|
||||
targetTableId: targetTable.id,
|
||||
sourceFieldId: sourceField.id,
|
||||
targetFieldId: targetField.id,
|
||||
sourceCardinality,
|
||||
targetCardinality,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.filter((rel) => rel !== null) as DBRelationship[];
|
||||
};
|
||||
228
src/lib/data/import-metadata/import/tables.ts
Normal file
228
src/lib/data/import-metadata/import/tables.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import type { DBIndex, DBTable } from '@/lib/domain';
|
||||
import {
|
||||
DatabaseType,
|
||||
generateTableKey,
|
||||
schemaNameToDomainSchemaName,
|
||||
} from '@/lib/domain';
|
||||
import type { DatabaseMetadata } from '../metadata-types/database-metadata';
|
||||
import type { TableInfo } from '../metadata-types/table-info';
|
||||
import { createAggregatedIndexes } from '../metadata-types/index-info';
|
||||
import {
|
||||
decodeBase64ToUtf16LE,
|
||||
decodeBase64ToUtf8,
|
||||
generateId,
|
||||
} from '@/lib/utils';
|
||||
import {
|
||||
defaultTableColor,
|
||||
materializedViewColor,
|
||||
viewColor,
|
||||
} from '@/lib/colors';
|
||||
import { createFieldsFromMetadata } from './fields';
|
||||
import { createIndexesFromMetadata } from './indexes';
|
||||
|
||||
export const decodeViewDefinition = (
|
||||
databaseType: DatabaseType,
|
||||
viewDefinition?: string
|
||||
): string => {
|
||||
if (!viewDefinition) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let decodedViewDefinition: string;
|
||||
if (databaseType === DatabaseType.SQL_SERVER) {
|
||||
decodedViewDefinition = decodeBase64ToUtf16LE(viewDefinition);
|
||||
} else {
|
||||
decodedViewDefinition = decodeBase64ToUtf8(viewDefinition);
|
||||
}
|
||||
|
||||
return decodedViewDefinition;
|
||||
};
|
||||
|
||||
export const createTablesFromMetadata = ({
|
||||
databaseMetadata,
|
||||
databaseType,
|
||||
}: {
|
||||
databaseMetadata: DatabaseMetadata;
|
||||
databaseType: DatabaseType;
|
||||
}): DBTable[] => {
|
||||
const {
|
||||
tables: tableInfos,
|
||||
pk_info: primaryKeys,
|
||||
columns,
|
||||
indexes,
|
||||
views: views,
|
||||
} = databaseMetadata;
|
||||
|
||||
// Pre-compute view names for faster lookup if there are views
|
||||
const viewNamesSet = new Set<string>();
|
||||
const materializedViewNamesSet = new Set<string>();
|
||||
|
||||
if (views && views.length > 0) {
|
||||
views.forEach((view) => {
|
||||
const key = generateTableKey({
|
||||
schemaName: view.schema,
|
||||
tableName: view.view_name,
|
||||
});
|
||||
viewNamesSet.add(key);
|
||||
|
||||
if (
|
||||
view.view_definition &&
|
||||
decodeViewDefinition(databaseType, view.view_definition)
|
||||
.toLowerCase()
|
||||
.includes('materialized')
|
||||
) {
|
||||
materializedViewNamesSet.add(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Pre-compute lookup maps for better performance
|
||||
const columnsByTable = new Map<string, (typeof columns)[0][]>();
|
||||
const indexesByTable = new Map<string, (typeof indexes)[0][]>();
|
||||
const primaryKeysByTable = new Map<string, (typeof primaryKeys)[0][]>();
|
||||
|
||||
// Group columns by table
|
||||
columns.forEach((col) => {
|
||||
const key = generateTableKey({
|
||||
schemaName: col.schema,
|
||||
tableName: col.table,
|
||||
});
|
||||
if (!columnsByTable.has(key)) {
|
||||
columnsByTable.set(key, []);
|
||||
}
|
||||
columnsByTable.get(key)!.push(col);
|
||||
});
|
||||
|
||||
// Group indexes by table
|
||||
indexes.forEach((idx) => {
|
||||
const key = generateTableKey({
|
||||
schemaName: idx.schema,
|
||||
tableName: idx.table,
|
||||
});
|
||||
if (!indexesByTable.has(key)) {
|
||||
indexesByTable.set(key, []);
|
||||
}
|
||||
indexesByTable.get(key)!.push(idx);
|
||||
});
|
||||
|
||||
// Group primary keys by table
|
||||
primaryKeys.forEach((pk) => {
|
||||
const key = generateTableKey({
|
||||
schemaName: pk.schema,
|
||||
tableName: pk.table,
|
||||
});
|
||||
if (!primaryKeysByTable.has(key)) {
|
||||
primaryKeysByTable.set(key, []);
|
||||
}
|
||||
primaryKeysByTable.get(key)!.push(pk);
|
||||
});
|
||||
|
||||
const result = tableInfos.map((tableInfo: TableInfo) => {
|
||||
const tableSchema = schemaNameToDomainSchemaName(tableInfo.schema);
|
||||
const tableKey = generateTableKey({
|
||||
schemaName: tableInfo.schema,
|
||||
tableName: tableInfo.table,
|
||||
});
|
||||
|
||||
// Use pre-computed lookups instead of filtering entire arrays
|
||||
const tableIndexes = indexesByTable.get(tableKey) || [];
|
||||
const tablePrimaryKeys = primaryKeysByTable.get(tableKey) || [];
|
||||
const tableColumns = columnsByTable.get(tableKey) || [];
|
||||
|
||||
// Aggregate indexes with multiple columns
|
||||
const aggregatedIndexes = createAggregatedIndexes({
|
||||
tableInfo,
|
||||
tableSchema,
|
||||
tableIndexes,
|
||||
});
|
||||
|
||||
const fields = createFieldsFromMetadata({
|
||||
aggregatedIndexes,
|
||||
tableColumns,
|
||||
tablePrimaryKeys,
|
||||
tableInfo,
|
||||
tableSchema,
|
||||
});
|
||||
|
||||
// Check for composite primary key and find matching index name
|
||||
const primaryKeyFields = fields.filter((f) => f.primaryKey);
|
||||
let pkMatchingIndexName: string | undefined;
|
||||
let pkIndex: DBIndex | undefined;
|
||||
|
||||
if (primaryKeyFields.length >= 1) {
|
||||
// We have a composite primary key, look for an index that matches all PK columns
|
||||
const pkFieldNames = primaryKeyFields.map((f) => f.name).sort();
|
||||
|
||||
// Find an index that matches the primary key columns exactly
|
||||
const matchingIndex = aggregatedIndexes.find((index) => {
|
||||
const indexColumnNames = index.columns
|
||||
.map((c) => c.name)
|
||||
.sort();
|
||||
return (
|
||||
indexColumnNames.length === pkFieldNames.length &&
|
||||
indexColumnNames.every((col, i) => col === pkFieldNames[i])
|
||||
);
|
||||
});
|
||||
|
||||
if (matchingIndex) {
|
||||
pkMatchingIndexName = matchingIndex.name;
|
||||
// Create a special PK index
|
||||
pkIndex = {
|
||||
id: generateId(),
|
||||
name: matchingIndex.name,
|
||||
unique: true,
|
||||
fieldIds: primaryKeyFields.map((f) => f.id),
|
||||
createdAt: Date.now(),
|
||||
isPrimaryKey: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out the index that matches the composite PK (to avoid duplication)
|
||||
const filteredAggregatedIndexes = pkMatchingIndexName
|
||||
? aggregatedIndexes.filter(
|
||||
(idx) => idx.name !== pkMatchingIndexName
|
||||
)
|
||||
: aggregatedIndexes;
|
||||
|
||||
const dbIndexes = createIndexesFromMetadata({
|
||||
aggregatedIndexes: filteredAggregatedIndexes,
|
||||
fields,
|
||||
});
|
||||
|
||||
// Add the PK index if it exists
|
||||
if (pkIndex) {
|
||||
dbIndexes.push(pkIndex);
|
||||
}
|
||||
|
||||
// Determine if the current table is a view by checking against pre-computed sets
|
||||
const viewKey = generateTableKey({
|
||||
schemaName: tableSchema,
|
||||
tableName: tableInfo.table,
|
||||
});
|
||||
const isView = viewNamesSet.has(viewKey);
|
||||
const isMaterializedView = materializedViewNamesSet.has(viewKey);
|
||||
|
||||
// Initial random positions; these will be adjusted later
|
||||
return {
|
||||
id: generateId(),
|
||||
name: tableInfo.table,
|
||||
schema: tableSchema,
|
||||
x: Math.random() * 1000, // Placeholder X
|
||||
y: Math.random() * 800, // Placeholder Y
|
||||
fields,
|
||||
indexes: dbIndexes,
|
||||
color: isMaterializedView
|
||||
? materializedViewColor
|
||||
: isView
|
||||
? viewColor
|
||||
: defaultTableColor,
|
||||
isView: isView,
|
||||
isMaterializedView: isMaterializedView,
|
||||
createdAt: Date.now(),
|
||||
comments: tableInfo.comment ? tableInfo.comment : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -2,7 +2,8 @@ const withExtras = false;
|
||||
const withDefault = `IFNULL(REPLACE(REPLACE(cols.column_default, '\\\\', ''), '"', 'ֿֿֿ\\"'), '')`;
|
||||
const withoutDefault = `""`;
|
||||
|
||||
export const mariaDBQuery = `SELECT CAST(CONCAT(
|
||||
export const mariaDBQuery = `SET SESSION group_concat_max_len = 10000000;
|
||||
SELECT CAST(CONCAT(
|
||||
'{"fk_info": [',
|
||||
IFNULL((SELECT GROUP_CONCAT(
|
||||
CONCAT('{"schema":"', cast(fk.table_schema as CHAR),
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { exportBaseSQL } from '../export-sql-script';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
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', () => {
|
||||
// Helper to generate test IDs and timestamps
|
||||
let idCounter = 0;
|
||||
@@ -116,7 +106,7 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// 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
|
||||
expect(sql).not.toMatch(/spell_id\s+uuid\s+NOT NULL\s+PRIMARY KEY/);
|
||||
expect(sql).not.toMatch(
|
||||
@@ -124,6 +114,96 @@ 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', () => {
|
||||
const diagram: Diagram = createDiagram({
|
||||
id: testId(),
|
||||
@@ -165,7 +245,7 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// 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
|
||||
expect(sql).not.toContain('PRIMARY KEY (id)');
|
||||
});
|
||||
@@ -226,8 +306,8 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
expect(sql).not.toContain('DEFAULT has default');
|
||||
expect(sql).not.toContain('DEFAULT DEFAULT has default');
|
||||
// The fields should still be in the table
|
||||
expect(sql).toContain('is_active boolean');
|
||||
expect(sql).toContain('stock_count int NOT NULL'); // integer gets simplified to int
|
||||
expect(sql).toContain('"is_active" boolean');
|
||||
expect(sql).toContain('"stock_count" integer NOT NULL'); // integer gets simplified to int
|
||||
});
|
||||
|
||||
it('should handle valid default values correctly', () => {
|
||||
@@ -349,8 +429,8 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// Should convert NOW to NOW() and ('now') to now()
|
||||
expect(sql).toContain('created_at timestamp DEFAULT NOW');
|
||||
expect(sql).toContain('updated_at timestamp DEFAULT now()');
|
||||
expect(sql).toContain('"created_at" timestamp DEFAULT NOW');
|
||||
expect(sql).toContain('"updated_at" timestamp DEFAULT now()');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -405,9 +485,9 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// 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
|
||||
expect(sql).toContain('status char(1)');
|
||||
expect(sql).toContain('"status" char(1)');
|
||||
});
|
||||
|
||||
it('should not have spaces between char and parentheses', () => {
|
||||
@@ -516,7 +596,7 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// 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');
|
||||
});
|
||||
|
||||
@@ -631,11 +711,11 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// Should create both tables
|
||||
expect(sql).toContain('CREATE TABLE guilds');
|
||||
expect(sql).toContain('CREATE TABLE guild_members');
|
||||
expect(sql).toContain('CREATE TABLE "guilds"');
|
||||
expect(sql).toContain('CREATE TABLE "guild_members"');
|
||||
// Should create foreign key
|
||||
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");'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -709,12 +789,9 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
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
|
||||
expect(sql).toContain('CREATE TABLE transportation.portals');
|
||||
expect(sql).toContain('CREATE TABLE magic.spells');
|
||||
expect(sql).toContain('CREATE TABLE "transportation"."portals"');
|
||||
expect(sql).toContain('CREATE TABLE "magic"."spells"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -761,7 +838,7 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// Should still create table structure
|
||||
expect(sql).toContain('CREATE TABLE empty_table');
|
||||
expect(sql).toContain('CREATE TABLE "empty_table"');
|
||||
expect(sql).toContain('(\n\n)');
|
||||
});
|
||||
|
||||
@@ -862,9 +939,9 @@ describe('DBML Export - SQL Generation Tests', () => {
|
||||
});
|
||||
|
||||
// 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
|
||||
expect(sql).toContain('interest_rate numeric(5)');
|
||||
expect(sql).toContain('"interest_rate" numeric(5)');
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user