Compare commits

...

80 Commits

Author SHA1 Message Date
Guy Ben-Aharon
24db32369a chore(main): release 1.3.0 (#417) 2024-11-25 19:14:51 +02:00
Guy Ben-Aharon
e77ee60a5b fix(i18n): add missing type to vi.ts (#444) 2024-11-25 14:42:14 +02:00
Huy Bui
6c65c2e9cc fix(i18n): add Vietnamese translations (#435) 2024-11-25 13:34:15 +02:00
Huy Bui
70f545f78b feat(side panel): collapsible side panel on desktop view + keyboard shortcut (#439)
* feat: collapsible sidebar on desktop view

* fix: remove unused wrapper

* feat: add toggle side panel hotkey
2024-11-25 13:14:48 +02:00
Jonathan Fishner
fb702c87ce fix(import script): remove double quotes (#442) 2024-11-24 18:25:14 +02:00
Jonathan Fishner
eaa067814f fix(templates): add Five more templates (bouncer, cabot, feedbin, Pythonic, flarum, freescout) (#441)
* feat(add templates): add three more templates (bouncer, cabot, feedbin)

* feat(add templates): add three more templates (Pythonic, flarum, freescout)

* fix build

* fix build
2024-11-24 18:22:50 +02:00
Guy Ben-Aharon
667685ed0f fix(dialogs): fix height of dialogs for small screens (#440)
* fix(dialogs): fix height of dialogs on small screens

* fix type
2024-11-24 12:21:15 +02:00
PRIYANSHI SHARMA
2940431efa fix(i18n): Translating to Gujarati language (#433)
* Translating to gujarati language good first issue #130

* fix build

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-23 19:39:13 +02:00
Guy Ben-Aharon
94ec43b608 fix: fix layout warnings (#434) 2024-11-21 20:30:00 +02:00
Guy Ben-Aharon
a2efed803f lint (#431) 2024-11-21 13:44:30 +02:00
VallabhaE
8749591be0 fix(i18n): Add Telugu Language (#352)
* Adding Telugu Language

* Adding Telugu Language

* revert package.json & package-lock.json

* fix build

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-21 11:35:35 +02:00
Guy Ben-Aharon
c5e0ea6fa4 fix(drawer): set fix min size (#429) 2024-11-20 18:45:32 +02:00
Wisnu Wicaksono
ab07da0b03 fix(i18n): add bahasa indonesia translation (#331)
* feat(i18n): add bahasa indonesia translation

* fix(i18n): update Indonesia name to Bahasa Indonesia in metadata

* fix(i18n): adjust top-navbar & add `id_ID` in `i18n`

* fix build

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-20 12:11:57 +02:00
☁️dungsil
8397bef392 fix(i18n): update korean for 1.2.0 (#419) 2024-11-19 11:53:06 +02:00
Zer0S2m
7c3c62860e fix(i18n): Translation of the export error into Russian (#418) 2024-11-19 11:45:48 +02:00
Guy Ben-Aharon
76ba4ce4c5 update canonical behavior on templates (#424) 2024-11-19 00:52:48 +02:00
Guy Ben-Aharon
0c0fad719f update canonical behavior on templates (#423) 2024-11-19 00:29:39 +02:00
Jonathan Fishner
b75c6fe4e7 fix(export-sql): add unique to export script (#422) 2024-11-18 21:28:14 +02:00
emircanakpinar
d9fcbeec72 fix(i18n): add Turkish translations (#315)
* fix(i18n): add missing Turkish translations

* fix translation build

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-18 16:00:30 +02:00
Guy Ben-Aharon
5d79721b6d update templates tag title (#420) 2024-11-18 14:30:12 +02:00
Guy Ben-Aharon
4be3592cf4 fix(share): fix export to handle broken indexes & relationships (#416) 2024-11-18 13:22:10 +02:00
Guy Ben-Aharon
b4cdcbbbd7 refactor template page - remove helmet data to a new component (#415)
* refactor template page - remove helmet data to a new component

* change texts

---------

Co-authored-by: johnnyfish <jonathanfishner11@gmail.com>
2024-11-18 12:28:04 +02:00
Jonathan Fishner
e9c7f4be06 Update README.md (#413)
* Update README.md

* Update README.md

* Update README.md
2024-11-17 14:14:50 +02:00
Guy Ben-Aharon
0530f9172c chore(main): release 1.2.0 (#396) 2024-11-17 11:55:44 +02:00
Twilight
e1e55c4b2a fix(i18n): add Nepali translations (#406)
* feat(i18n): add nepali locale

* feat(i18n): add nepali locale

* feat: add missing translations

* fix build + add type declaration

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-17 11:51:37 +02:00
ABHISHEK FADAKE
c6f7ff70f8 fix(i18n): Create Translations in Marathi language (#266)
* Create Translations in marathi language

* Remaining code for adding marathi  i18n support

* fix build

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-17 11:18:34 +02:00
Guy Ben-Aharon
02aaabdc4e fix(i18n): fix language nav: close when lang selected, hide tooltip when lang selected (#411) 2024-11-16 20:29:48 +02:00
Jonathan Fishner
0f673947af fix(templates): add five more templates (Sylius, Monica, Attendize, SaaS Pegasus & BookStack) (#408) 2024-11-16 17:06:23 +02:00
Guy Ben-Aharon
f35f62fdf3 fix(i18n): change language keeps selected language also after refreshing the page (#409) 2024-11-16 17:02:20 +02:00
Guy Ben-Aharon
68474e75d5 fix(export image): Add support for displaying cardinality relationships + background (#407) 2024-11-16 16:57:13 +02:00
Nic
eaf75cedb0 fix(dockerfile): support SPA refresh to resolve nginx return 404 (#384)
* feat(dockerfile): support SPA refresh to resolve nginx return 404

* remove comments & set server_name to default

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-14 20:00:14 +02:00
Guy Ben-Aharon
fe8b9f9e91 fix(templates): fix tags urls (#405) 2024-11-14 18:30:10 +02:00
Jonathan Fishner
07d3745747 fix(templates): add six more templates (ticketit, snipe-it, refinerycms, comfortable-mexican-sofa, buddypress, lobsters) (#402) 2024-11-14 17:42:04 +02:00
Guy Ben-Aharon
44cf5ca264 feat(duplicate table): duplicate table from the canvas and sidebar (#404)
* feat(duplicate table): duplicate table from the canvas and sidebar

* feat(duplicate table): underscore instead of space

* feat(duplicate table): underscore instead of space
2024-11-14 17:35:53 +02:00
Emmanuel Ferdman
44d10c2390 fix(docs): update license reference (#403)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2024-11-14 17:26:20 +02:00
Guy Ben-Aharon
9698821828 export clone (diagram & table) logic (#401) 2024-11-14 15:11:21 +02:00
Guy Ben-Aharon
9f8500fc7e fix(templates): fix cloned indexes from a template (#398) 2024-11-13 18:58:50 +02:00
Jonathan Fishner
e5dbbf2eaa fix(AI exports): add cahching layer to SQL exports (#390)
* fix(AI exports): add cahching layer to SQL exports

* remove logs

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-13 17:18:27 +02:00
Guy Ben-Aharon
959e5402b8 fix(templates): tag urls lowercase to support browsers (#397) 2024-11-13 16:38:10 +02:00
Guy Ben-Aharon
492c9324d2 fix(canvas): fix auto zoom on diagram load (#395) 2024-11-13 16:17:03 +02:00
Guy Ben-Aharon
42c159605d chore(main): release 1.1.0 (#364) 2024-11-13 15:26:17 +02:00
Guy Ben-Aharon
78c427f38e fix(templates): fix issue with double-clone on localhost (#394) 2024-11-13 15:22:00 +02:00
Jonathan Fishner
bae74d1693 feat(add templates) add five more templates (gravity, koel.dev, laravel-permission, laravel-spark, voyager) (#392) 2024-11-13 13:24:01 +02:00
Ian Cheng
123f40f39e fix(i18n): added traditional Chinese language translation (#356)
* feat: added traditional chinese language translation

* feat: added traditional chinese language translation

---------

Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2024-11-12 17:26:42 +02:00
ntoniazzi
e3129cec74 fix(i18n): french translation update - share menu (#391) 2024-11-12 17:22:40 +02:00
Eva
5508c1e084 fix(i18n): Fixed part of RU lang introduced in #365 feat(share) (#380)
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2024-11-12 13:43:36 +02:00
lkjhxx
9f2893319a fix(i18n): Add simplified chinese (#385)
* feat: add Simplified Chinese

* feat: add Simplified Chinese

* fix linter Update zh_CN.ts

---------

Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2024-11-12 13:23:57 +02:00
Guy Ben-Aharon
125a39fb5b fix(sql export): make loading for export interactive (#388) 2024-11-12 12:37:56 +02:00
Guy Ben-Aharon
4ca1832732 fix(bundle): fix bundle size (#382) 2024-11-11 01:13:44 +02:00
Guy Ben-Aharon
3609bfea4d fix(share): add loader to the export (#381)
* fix(share): add loader to the export

* fix(share): add loader to the export
2024-11-10 23:33:16 +02:00
Guy Ben-Aharon
94a5d84fae feat(share): add sharing capabilities to import and export diagrams (#365)
* feat(share): add sharing capabilities to import and export diagrams

* remove use client

* fix build

* add error parse indication

* add import from initial dialog

* fix build
2024-11-10 16:30:15 +02:00
Jonathan Fishner
85e691fcbe fix for tempalte name novel database (#379) 2024-11-10 09:02:51 +02:00
Daniel Cruz
709ccff8fa Adds missing spanish translations (#372)
fix(translations): Add missing Spanish translations
2024-11-09 20:06:10 +02:00
Elton Costa
6c7eb4609d feat(canvas): Added Snap to grid functionality. Toggle/hold shift to enable snap to grid. (#373)
* asd

* add translations & useKeyPress

* fix build

* fix build

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-09 19:55:40 +02:00
Eva
2c69b08eae fix(i18n): Added Russian language (#376)
* (i18n): Added Russian language.

* Update src/i18n/locales/ru.ts

Co-authored-by: Eva <29357907+nikelborm@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Eva <29357907+nikelborm@users.noreply.github.com>

* Refined and added missing fields to RU translation

* Refined and added missing fields to RU translation

---------

Co-authored-by: Aditya Kale <kaleaditya779@gmail.com>
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2024-11-09 19:01:36 +02:00
Favor
84e7591d05 fix: improve title name edit interaction (#367)
* chore(main): improve table name edit interaction

* chore: fix lint issues

* feat(i18n): add double-click functionality and tooltip translations

- Change editable titles action to `onDoubleClick`
- Add tooltip translations for table name editing feature
- Support for 9 languages: Russian, Japanese, Hindi, French, Spanish,
  German, Ukrainian, Portuguese, and Korean
- Improve UX by indicating double-click edit functionality across languages

* naming + some padding

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-09 15:41:22 +02:00
orig
545e8578c9 fix(dockerfile): support openai key in docker build (#366) 2024-11-09 14:19:02 +02:00
Jonathan Fishner
f1d073d053 fix(templates): change the template url to be database instead of db (#374)
* fix(templates): change the template url to be database instead of db

* add tag

* layout fixes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-09 14:02:29 +02:00
Jonathan Fishner
20b3396ec2 feat(add templates): add five more templates (laravel, django, twitter… (#371)
* feat(add templates): add five more templates (laravel, django, twitter, adonis-acl, akaunting)

* fix build

* fix tags

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-09 03:38:03 +02:00
☁️dungsil
b305be82ae fix(i18n): add korean (#362)
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2024-11-07 16:27:24 +02:00
Jonathan Fishner
1430d2c236 fix(import json): for Check Script Result, default with quotes (#358) 2024-11-07 16:18:56 +02:00
Guy Ben-Aharon
acf8ade23c chore(main): release 1.0.1 (#320) 2024-11-07 00:05:51 +02:00
Guy Ben-Aharon
aa884b49ce fix(offline): add support when running on isolated network (#359) 2024-11-06 23:56:05 +02:00
Jonathan Fishner
acb736e44f fix(smart query): import postgres FKs (#357) 2024-11-06 22:49:32 +02:00
Guy Ben-Aharon
180886c588 fix(template): separator in case of empty url (#355) 2024-11-06 18:24:25 +02:00
Guy Ben-Aharon
e993476fad add template url (#354) 2024-11-06 18:07:02 +02:00
Guy Ben-Aharon
efaddeebb4 fix(templates): align database icon (#351) 2024-11-06 13:41:28 +02:00
Francis Chartrand
93f623a13a fix(select-box): allow using tab & space to show choices (#336)
* styles(select-box): allow using tab & space to show choices

* use code instead of key

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-06 13:29:25 +02:00
Guy Ben-Aharon
87a40cff61 fix: open default diagram after deleting current diagram (#350) 2024-11-06 12:01:49 +02:00
Guy Ben-Aharon
f00c9b9a03 refactor languages menu (#347) 2024-11-06 10:33:44 +02:00
Помаранча
20b2ae436c Add uk language (#338)
* Create uk.ts

added Ukrainian language. I don't know what kind of service it is, but I just helped with the translation into my native language

* Update uk.ts

now all untranslated item (2) is translated

* fix build

* add language to menu

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-06 10:20:14 +02:00
Guy Ben-Aharon
820a4640da template title change (#346) 2024-11-06 09:52:21 +02:00
Jonathan Fishner
0193853035 add pokemon database to our templates (#335) 2024-11-05 21:23:31 +02:00
Jonathan Fishner
b40344675e Update README.md (#332) 2024-11-05 14:57:24 +02:00
Guy Ben-Aharon
df7e687f61 chartdb.png image update (#330) 2024-11-05 14:03:55 +02:00
Guy Ben-Aharon
ad10d26f13 chartdb.png image name update (#329) 2024-11-05 13:58:48 +02:00
Jonathan Fishner
588c64b380 Tempaltes keywords (#325)
* fix for tempaltes keywords

* remove keywords

* update templates description

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-05 13:40:57 +02:00
Guy Ben-Aharon
3d3efc5e82 add canonical link to templates (#327) 2024-11-05 10:54:05 +02:00
Guy Ben-Aharon
d8a20ebbd9 fix(templates): fetch templates data from router (#321) 2024-11-04 18:01:04 +02:00
Jonathan Fishner
ebce8827ea fix(templates): add two more templates (Airbnb, Wordpress) (#317)
* add two more templates (Airbnb, Wordpress)

* fix slugs

* fix templates sizes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-04 15:41:51 +02:00
179 changed files with 90944 additions and 1293 deletions

View File

@@ -1,5 +1,97 @@
# Changelog # Changelog
## [1.3.0](https://github.com/chartdb/chartdb/compare/v1.2.0...v1.3.0) (2024-11-25)
### Features
* **side panel:** collapsible side panel on desktop view + keyboard shortcut ([#439](https://github.com/chartdb/chartdb/issues/439)) ([70f545f](https://github.com/chartdb/chartdb/commit/70f545f78bab9c510a6e5936fa5b259b806b6c69))
### Bug Fixes
* **dialogs:** fix height of dialogs for small screens ([#440](https://github.com/chartdb/chartdb/issues/440)) ([667685e](https://github.com/chartdb/chartdb/commit/667685ed0f6a8cc61ae86b3ba60e052fbe6a9e1a))
* **drawer:** set fix min size ([#429](https://github.com/chartdb/chartdb/issues/429)) ([c5e0ea6](https://github.com/chartdb/chartdb/commit/c5e0ea6fa4017666ff3bc1e3071c487df48afd3d))
* **export-sql:** add unique to export script ([#422](https://github.com/chartdb/chartdb/issues/422)) ([b75c6fe](https://github.com/chartdb/chartdb/commit/b75c6fe4e78f3e2058be680f2fa0442db3b4a6bd))
* fix layout warnings ([#434](https://github.com/chartdb/chartdb/issues/434)) ([94ec43b](https://github.com/chartdb/chartdb/commit/94ec43b60845bb8c3592ce1b1450ca0171a53f99))
* **i18n:** add bahasa indonesia translation ([#331](https://github.com/chartdb/chartdb/issues/331)) ([ab07da0](https://github.com/chartdb/chartdb/commit/ab07da0b031f0d4050ff6b44ddcb94cb6c0010b6))
* **i18n:** add missing type to vi.ts ([#444](https://github.com/chartdb/chartdb/issues/444)) ([e77ee60](https://github.com/chartdb/chartdb/commit/e77ee60a5b47e0854d11b0ee2f16d6956737d0ff))
* **i18n:** Add Telugu Language ([#352](https://github.com/chartdb/chartdb/issues/352)) ([8749591](https://github.com/chartdb/chartdb/commit/8749591be036e131de4bfeed1e6eece8d62980dd))
* **i18n:** add Turkish translations ([#315](https://github.com/chartdb/chartdb/issues/315)) ([d9fcbee](https://github.com/chartdb/chartdb/commit/d9fcbeec726b7bde9f7d202bf09dc6b617e3ad80))
* **i18n:** add Vietnamese translations ([#435](https://github.com/chartdb/chartdb/issues/435)) ([6c65c2e](https://github.com/chartdb/chartdb/commit/6c65c2e9cce600b9778b84ce5b5f1625dc6f1a58))
* **i18n:** Translating to Gujarati language ([#433](https://github.com/chartdb/chartdb/issues/433)) ([2940431](https://github.com/chartdb/chartdb/commit/2940431efa1a6aa54d80c61d5e05f0ad47cd67ba))
* **i18n:** Translation of the export error into Russian ([#418](https://github.com/chartdb/chartdb/issues/418)) ([7c3c628](https://github.com/chartdb/chartdb/commit/7c3c62860efc98d3aabf2132a79ac945ffc8315a))
* **i18n:** update korean for 1.2.0 ([#419](https://github.com/chartdb/chartdb/issues/419)) ([8397bef](https://github.com/chartdb/chartdb/commit/8397bef3924610d94661aae99c55ba4fa376a186))
* **import script:** remove double quotes ([#442](https://github.com/chartdb/chartdb/issues/442)) ([fb702c8](https://github.com/chartdb/chartdb/commit/fb702c87ce5254bf6e0209c692305f5086956090))
* **share:** fix export to handle broken indexes & relationships ([#416](https://github.com/chartdb/chartdb/issues/416)) ([4be3592](https://github.com/chartdb/chartdb/commit/4be3592cf4d160be83ddf1db01ffe9afdef119fa))
* **templates:** add Five more templates (bouncer, cabot, feedbin, Pythonic, flarum, freescout) ([#441](https://github.com/chartdb/chartdb/issues/441)) ([eaa0678](https://github.com/chartdb/chartdb/commit/eaa067814fd96fcc1ee10488ee747a71a8e8ec7a))
## [1.2.0](https://github.com/chartdb/chartdb/compare/v1.1.0...v1.2.0) (2024-11-17)
### Features
* **duplicate table:** duplicate table from the canvas and sidebar ([#404](https://github.com/chartdb/chartdb/issues/404)) ([44cf5ca](https://github.com/chartdb/chartdb/commit/44cf5ca264f52851f2dffb51a752a52b6fa7ec8d))
### Bug Fixes
* **AI exports:** add cahching layer to SQL exports ([#390](https://github.com/chartdb/chartdb/issues/390)) ([e5dbbf2](https://github.com/chartdb/chartdb/commit/e5dbbf2eaab6d80a531d451211b6f5a415bc7ce3))
* **canvas:** fix auto zoom on diagram load ([#395](https://github.com/chartdb/chartdb/issues/395)) ([492c932](https://github.com/chartdb/chartdb/commit/492c9324d27b561470c4967ce2e99f82eec467d8))
* **dockerfile:** support SPA refresh to resolve nginx return 404 ([#384](https://github.com/chartdb/chartdb/issues/384)) ([eaf75ce](https://github.com/chartdb/chartdb/commit/eaf75cedb0e024236c7684bb533856d7f80074da))
* **docs:** update license reference ([#403](https://github.com/chartdb/chartdb/issues/403)) ([44d10c2](https://github.com/chartdb/chartdb/commit/44d10c23907165288951a9d2ec3165ad23f81c61))
* **export image:** Add support for displaying cardinality relationships + background ([#407](https://github.com/chartdb/chartdb/issues/407)) ([68474e7](https://github.com/chartdb/chartdb/commit/68474e75d56ed4b4b445cc9b7f59cca96a4ca5db))
* **i18n:** add Nepali translations ([#406](https://github.com/chartdb/chartdb/issues/406)) ([e1e55c4](https://github.com/chartdb/chartdb/commit/e1e55c4b2ac7755b0810dc1f21da44903fe68a54))
* **i18n:** change language keeps selected language also after refreshing the page ([#409](https://github.com/chartdb/chartdb/issues/409)) ([f35f62f](https://github.com/chartdb/chartdb/commit/f35f62fdf38ca84065f171a31b80aa8123b1d8b9))
* **i18n:** Create Translations in Marathi language ([#266](https://github.com/chartdb/chartdb/issues/266)) ([c6f7ff7](https://github.com/chartdb/chartdb/commit/c6f7ff70f841efb9cf1766338f409fe0ea7bb998))
* **i18n:** fix language nav: close when lang selected, hide tooltip when lang selected ([#411](https://github.com/chartdb/chartdb/issues/411)) ([02aaabd](https://github.com/chartdb/chartdb/commit/02aaabdc4e9b1570d81ff03fe1e6da0307f22999))
* **templates:** add five more templates (Sylius, Monica, Attendize, SaaS Pegasus & BookStack) ([#408](https://github.com/chartdb/chartdb/issues/408)) ([0f67394](https://github.com/chartdb/chartdb/commit/0f673947af469e86f70737427ac8fb3c2420d1a2))
* **templates:** add six more templates (ticketit, snipe-it, refinerycms, comfortable-mexican-sofa, buddypress, lobsters) ([#402](https://github.com/chartdb/chartdb/issues/402)) ([07d3745](https://github.com/chartdb/chartdb/commit/07d374574775d132e1cba0908c47dcbbd6cd2c3f))
* **templates:** fix cloned indexes from a template ([#398](https://github.com/chartdb/chartdb/issues/398)) ([9f8500f](https://github.com/chartdb/chartdb/commit/9f8500fc7e36e6a819ecb9029f263d80eac88279))
* **templates:** fix tags urls ([#405](https://github.com/chartdb/chartdb/issues/405)) ([fe8b9f9](https://github.com/chartdb/chartdb/commit/fe8b9f9e91481d8a3272113b6f4be4da8d61ad04))
* **templates:** tag urls lowercase to support browsers ([#397](https://github.com/chartdb/chartdb/issues/397)) ([959e540](https://github.com/chartdb/chartdb/commit/959e5402b8c112fae6243ce9283947057506c128))
## [1.1.0](https://github.com/chartdb/chartdb/compare/v1.0.1...v1.1.0) (2024-11-13)
### Features
* **add templates:** add five more templates (laravel, django, twitter… ([#371](https://github.com/chartdb/chartdb/issues/371)) ([20b3396](https://github.com/chartdb/chartdb/commit/20b3396ec2afff09ca8bcdd91f5c6284c93cd959))
* **canvas:** Added Snap to grid functionality. Toggle/hold shift to enable snap to grid. ([#373](https://github.com/chartdb/chartdb/issues/373)) ([6c7eb46](https://github.com/chartdb/chartdb/commit/6c7eb4609d8466278de30317665929ec529c1f94))
* **share:** add sharing capabilities to import and export diagrams ([#365](https://github.com/chartdb/chartdb/issues/365)) ([94a5d84](https://github.com/chartdb/chartdb/commit/94a5d84fae819b0de6c1e471d1aad16dc8f39dd6))
### Bug Fixes
* **bundle:** fix bundle size ([#382](https://github.com/chartdb/chartdb/issues/382)) ([4ca1832](https://github.com/chartdb/chartdb/commit/4ca18327324106950f0d1af851b9b74379b67b7b))
* **dockerfile:** support openai key in docker build ([#366](https://github.com/chartdb/chartdb/issues/366)) ([545e857](https://github.com/chartdb/chartdb/commit/545e8578c9e8aa71696f6aa8bec81cacaa602c2d))
* **i18n:** add korean ([#362](https://github.com/chartdb/chartdb/issues/362)) ([b305be8](https://github.com/chartdb/chartdb/commit/b305be82aee00994ef576ca6fd62d72dd491f771))
* **i18n:** Add simplified chinese ([#385](https://github.com/chartdb/chartdb/issues/385)) ([9f28933](https://github.com/chartdb/chartdb/commit/9f2893319a1a2aed9a7c03d15e25a17ab37c2465))
* **i18n:** Added Russian language ([#376](https://github.com/chartdb/chartdb/issues/376)) ([2c69b08](https://github.com/chartdb/chartdb/commit/2c69b08eaea6b86ce0c1ddb18a23e22629198bf5))
* **i18n:** added traditional Chinese language translation ([#356](https://github.com/chartdb/chartdb/issues/356)) ([123f40f](https://github.com/chartdb/chartdb/commit/123f40f39e703ad612635964af530ac72c387d3c))
* **i18n:** Fixed part of RU lang introduced in [#365](https://github.com/chartdb/chartdb/issues/365) feat(share) ([#380](https://github.com/chartdb/chartdb/issues/380)) ([5508c1e](https://github.com/chartdb/chartdb/commit/5508c1e084e0ee24d1a54f721f760b9fc14df107))
* **i18n:** french translation update - share menu ([#391](https://github.com/chartdb/chartdb/issues/391)) ([e3129ce](https://github.com/chartdb/chartdb/commit/e3129cec744d18f09953544d9e74cd5adc4e8afb))
* **import json:** for Check Script Result, default with quotes ([#358](https://github.com/chartdb/chartdb/issues/358)) ([1430d2c](https://github.com/chartdb/chartdb/commit/1430d2c2365b7b74e36b8ff9d32a163d7437448a))
* improve title name edit interaction ([#367](https://github.com/chartdb/chartdb/issues/367)) ([84e7591](https://github.com/chartdb/chartdb/commit/84e7591d0586b9a457f31737c6e363ef41574142))
* **share:** add loader to the export ([#381](https://github.com/chartdb/chartdb/issues/381)) ([3609bfe](https://github.com/chartdb/chartdb/commit/3609bfea4d4c78b03711ff8d721b4e67bf82185a))
* **sql export:** make loading for export interactive ([#388](https://github.com/chartdb/chartdb/issues/388)) ([125a39f](https://github.com/chartdb/chartdb/commit/125a39fb5be803f0e6db0b68fb5bc8e290fa8dae))
* **templates:** change the template url to be database instead of db ([#374](https://github.com/chartdb/chartdb/issues/374)) ([f1d073d](https://github.com/chartdb/chartdb/commit/f1d073d05383955da6f60a9a66ed2be879b103e4))
* **templates:** fix issue with double-clone on localhost ([#394](https://github.com/chartdb/chartdb/issues/394)) ([78c427f](https://github.com/chartdb/chartdb/commit/78c427f38e5c64fc340d13ceb2153c2b85db437e))
## [1.0.1](https://github.com/chartdb/chartdb/compare/v1.0.0...v1.0.1) (2024-11-06)
### Bug Fixes
* **offline:** add support when running on isolated network ([#359](https://github.com/chartdb/chartdb/issues/359)) ([aa884b4](https://github.com/chartdb/chartdb/commit/aa884b49ce16d70f67881bdc940993c1fe901796))
* open default diagram after deleting current diagram ([#350](https://github.com/chartdb/chartdb/issues/350)) ([87a40cf](https://github.com/chartdb/chartdb/commit/87a40cff615b04b678642ba2d6e097c38b26d239))
* **select-box:** allow using tab & space to show choices ([#336](https://github.com/chartdb/chartdb/issues/336)) ([93f623a](https://github.com/chartdb/chartdb/commit/93f623a13a61e9143638fbe7e8346f07e37a26b2))
* **smart query:** import postgres FKs ([#357](https://github.com/chartdb/chartdb/issues/357)) ([acb736e](https://github.com/chartdb/chartdb/commit/acb736e44fd50d29a85b4eff42e20780aef710ed))
* **templates:** add two more templates (Airbnb, Wordpress) ([#317](https://github.com/chartdb/chartdb/issues/317)) ([ebce882](https://github.com/chartdb/chartdb/commit/ebce8827eab049eefa0eebcb0ec2540698bc0e15))
* **templates:** align database icon ([#351](https://github.com/chartdb/chartdb/issues/351)) ([efaddee](https://github.com/chartdb/chartdb/commit/efaddeebb4f24235d82f4e2bf7423fbf48b97187))
* **template:** separator in case of empty url ([#355](https://github.com/chartdb/chartdb/issues/355)) ([180886c](https://github.com/chartdb/chartdb/commit/180886c5882f2329c797fc284b255012d21f5b5c))
* **templates:** fetch templates data from router ([#321](https://github.com/chartdb/chartdb/issues/321)) ([d8a20eb](https://github.com/chartdb/chartdb/commit/d8a20ebbd9118989690a40fcd3aa59fb156b446f))
## 1.0.0 (2024-11-04) ## 1.0.0 (2024-11-04)

View File

@@ -30,7 +30,7 @@ To get started:
### License ### License
By contributing, you agree that your work will be licensed under ChartDB's [license](https://github.com/chartdb/chartdb/blob/main/LICENSE.md). By contributing, you agree that your work will be licensed under ChartDB's [license](https://github.com/chartdb/chartdb/blob/main/LICENSE).
## Questions? ## Questions?

View File

@@ -1,5 +1,7 @@
FROM node:22-alpine AS builder FROM node:22-alpine AS builder
ARG VITE_OPENAI_API_KEY
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
@@ -14,6 +16,7 @@ RUN npm run build
FROM nginx:stable-alpine AS production FROM nginx:stable-alpine AS production
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
COPY ./default.conf /etc/nginx/conf.d/default.conf
# Expose the default port for the Nginx web server # Expose the default port for the Nginx web server
EXPOSE 80 EXPOSE 80

View File

@@ -15,8 +15,9 @@
<h3 align="center"> <h3 align="center">
<a href="https://discord.gg/QeFwyWSKwC">Community</a> &bull; <a href="https://discord.gg/QeFwyWSKwC">Community</a> &bull;
<a href="https://www.chartdb.io">Website</a> &bull; <a href="https://www.chartdb.io?ref=github_readme">Website</a> &bull;
<a href="https://app.chartdb.io/examples">Demo</a> <a href="https://chartdb.io/templates?ref=github_readme">Examples</a> &bull;
<a href="https://app.chartdb.io?ref=github_readme">Demo</a>
</h3> </h3>
<h4 align="center"> <h4 align="center">
@@ -38,7 +39,7 @@
--- ---
<p align="center"> <p align="center">
<img width='700px' src="./public/ChartDB.png"> <img width='700px' src="./public/chartdb.png">
</p> </p>
### 🎉 ChartDB ### 🎉 ChartDB
@@ -71,7 +72,7 @@ ChartDB is currently in Public Beta. Star and watch this repository to get notif
## Getting Started ## Getting Started
Use the [cloud version](https://app.chartdb.io/) or deploy locally: Use the [cloud version](https://app.chartdb.io?ref=github_readme_2) or deploy locally:
### How To Use ### How To Use
@@ -97,7 +98,7 @@ VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
### Running the Docker Container ### Running the Docker Container
```bash ```bash
docker build -t chartdb . docker build -t chartdb . (If you want AI capabilities, use `docker build --build-arg VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -t chartdb .`)
docker run -p 8080:80 chartdb docker run -p 8080:80 chartdb
``` ```
@@ -105,7 +106,7 @@ Open your browser and navigate to `http://localhost:8080`.
## Try it on our website ## Try it on our website
1. Go to [ChartDB.io](https://chartdb.io) 1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)
2. Click "Go to app" 2. Click "Go to app"
3. Choose the database that you are using. 3. Choose the database that you are using.
4. Take the magic query and run it in your database. 4. Take the magic query and run it in your database.

15
default.conf Normal file
View File

@@ -0,0 +1,15 @@
server {
listen 80;
listen [::]:80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

18
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "chartdb", "name": "chartdb",
"version": "1.0.0", "version": "1.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "chartdb", "name": "chartdb",
"version": "1.0.0", "version": "1.3.0",
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^0.0.51", "@ai-sdk/openai": "^0.0.51",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
@@ -44,6 +44,7 @@
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"i18next": "^23.14.0", "i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.441.0", "lucide-react": "^0.441.0",
"monaco-editor": "^0.52.0", "monaco-editor": "^0.52.0",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
@@ -60,7 +61,8 @@
"tailwind-merge": "^2.4.0", "tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.6", "timeago-react": "^3.0.6",
"vaul": "^0.9.1" "vaul": "^0.9.1",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.1.0", "@types/node": "^22.1.0",
@@ -6754,6 +6756,15 @@
"@babel/runtime": "^7.23.2" "@babel/runtime": "^7.23.2"
} }
}, },
"node_modules/i18next-browser-languagedetector": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -10539,7 +10550,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

View File

@@ -1,95 +1,97 @@
{ {
"name": "chartdb", "name": "chartdb",
"private": true, "private": true,
"version": "1.0.0", "version": "1.3.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "npm run lint && tsc -b && vite build", "build": "npm run lint && tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "npm run lint -- --fix", "lint:fix": "npm run lint -- --fix",
"preview": "vite preview", "preview": "vite preview",
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^0.0.51", "@ai-sdk/openai": "^0.0.51",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1", "@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1", "@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@uidotdev/usehooks": "^2.4.1", "@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.3.1", "@xyflow/react": "^12.3.1",
"ahooks": "^3.8.1", "ahooks": "^3.8.1",
"ai": "^3.3.14", "ai": "^3.3.14",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"dexie": "^4.0.8", "dexie": "^4.0.8",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"i18next": "^23.14.0", "i18next": "^23.14.0",
"lucide-react": "^0.441.0", "i18next-browser-languagedetector": "^8.0.0",
"monaco-editor": "^0.52.0", "lucide-react": "^0.441.0",
"nanoid": "^5.0.7", "monaco-editor": "^0.52.0",
"node-sql-parser": "^5.3.2", "nanoid": "^5.0.7",
"react": "^18.3.1", "node-sql-parser": "^5.3.2",
"react-dom": "^18.3.1", "react": "^18.3.1",
"react-helmet-async": "^2.0.5", "react-dom": "^18.3.1",
"react-hotkeys-hook": "^4.5.0", "react-helmet-async": "^2.0.5",
"react-i18next": "^15.0.1", "react-hotkeys-hook": "^4.5.0",
"react-resizable-panels": "^2.0.22", "react-i18next": "^15.0.1",
"react-responsive": "^10.0.0", "react-resizable-panels": "^2.0.22",
"react-router-dom": "^6.26.0", "react-responsive": "^10.0.0",
"react-use": "^17.5.1", "react-router-dom": "^6.26.0",
"tailwind-merge": "^2.4.0", "react-use": "^17.5.1",
"tailwindcss-animate": "^1.0.7", "tailwind-merge": "^2.4.0",
"timeago-react": "^3.0.6", "tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.1" "timeago-react": "^3.0.6",
}, "vaul": "^0.9.1",
"devDependencies": { "zod": "^3.23.8"
"@types/node": "^22.1.0", },
"@types/react": "^18.3.3", "devDependencies": {
"@types/react-dom": "^18.3.0", "@types/node": "^22.1.0",
"@typescript-eslint/eslint-plugin": "^7.15.0", "@types/react": "^18.3.3",
"@typescript-eslint/parser": "^7.15.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1", "@typescript-eslint/eslint-plugin": "^7.15.0",
"autoprefixer": "^10.4.20", "@typescript-eslint/parser": "^7.15.0",
"eslint": "^8.57.0", "@vitejs/plugin-react": "^4.3.1",
"eslint-config-prettier": "^9.1.0", "autoprefixer": "^10.4.20",
"eslint-plugin-css-modules": "^2.12.0", "eslint": "^8.57.0",
"eslint-plugin-jsx-a11y": "^6.9.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-react": "^7.35.0", "eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-react": "^7.35.0",
"eslint-plugin-tailwindcss": "^3.17.4", "eslint-plugin-react-hooks": "^4.6.2",
"husky": "^9.1.5", "eslint-plugin-react-refresh": "^0.4.7",
"postcss": "^8.4.40", "eslint-plugin-tailwindcss": "^3.17.4",
"prettier": "^3.3.3", "husky": "^9.1.5",
"rollup-plugin-visualizer": "^5.12.0", "postcss": "^8.4.40",
"tailwindcss": "^3.4.7", "prettier": "^3.3.3",
"typescript": "^5.2.2", "rollup-plugin-visualizer": "^5.12.0",
"unplugin-inject-preload": "^3.0.0", "tailwindcss": "^3.4.7",
"vite": "^5.3.4" "typescript": "^5.2.2",
} "unplugin-inject-preload": "^3.0.0",
"vite": "^5.3.4"
}
} }

View File

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 882 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

View File

@@ -0,0 +1,62 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: 'default',
},
}
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn(
'mb-1 font-medium leading-none tracking-tight',
className
)}
{...props}
/>
));
AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
));
AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertTitle, AlertDescription };

View File

@@ -9,12 +9,15 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { DarkTheme } from './themes/dark'; import { DarkTheme } from './themes/dark';
import { LightTheme } from './themes/light'; import { LightTheme } from './themes/light';
import './config.ts';
export interface CodeSnippetProps { export interface CodeSnippetProps {
className?: string; className?: string;
code: string; code: string;
language?: 'sql' | 'shell'; language?: 'sql' | 'shell';
loading?: boolean; loading?: boolean;
autoScroll?: boolean;
isComplete?: boolean;
} }
export const Editor = lazy(() => export const Editor = lazy(() =>
@@ -24,7 +27,14 @@ export const Editor = lazy(() =>
); );
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo( export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
({ className, code, loading, language = 'sql' }) => { ({
className,
code,
loading,
language = 'sql',
autoScroll = false,
isComplete = true,
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const monaco = useMonaco(); const monaco = useMonaco();
const { effectiveTheme } = useTheme(); const { effectiveTheme } = useTheme();
@@ -46,6 +56,16 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
}, 1500); }, 1500);
}, [isCopied]); }, [isCopied]);
useEffect(() => {
if (monaco) {
const editor = monaco.editor.getModels()[0];
if (editor && autoScroll) {
const lineCount = editor.getLineCount();
monaco.editor.getEditors()[0]?.revealLine(lineCount);
}
}
}, [code, monaco, autoScroll]);
const copyToClipboard = useCallback(() => { const copyToClipboard = useCallback(() => {
navigator.clipboard.writeText(code); navigator.clipboard.writeText(code);
setIsCopied(true); setIsCopied(true);
@@ -62,32 +82,38 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
<Spinner /> <Spinner />
) : ( ) : (
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
<Tooltip {isComplete ? (
onOpenChange={setTooltipOpen} <Tooltip
open={isCopied || tooltipOpen} onOpenChange={setTooltipOpen}
> open={isCopied || tooltipOpen}
<TooltipTrigger
asChild
className="absolute right-1 top-1 z-10"
> >
<span> <TooltipTrigger
<Button asChild
className=" h-fit p-1.5" className="absolute right-1 top-1 z-10"
variant="outline" >
onClick={copyToClipboard} <span>
> <Button
{isCopied ? ( className=" h-fit p-1.5"
<CopyCheck size={16} /> variant="outline"
) : ( onClick={copyToClipboard}
<Copy size={16} /> >
)} {isCopied ? (
</Button> <CopyCheck size={16} />
</span> ) : (
</TooltipTrigger> <Copy size={16} />
<TooltipContent> )}
{t(isCopied ? 'copied' : 'copy_to_clipboard')} </Button>
</TooltipContent> </span>
</Tooltip> </TooltipTrigger>
<TooltipContent>
{t(
isCopied
? 'copied'
: 'copy_to_clipboard'
)}
</TooltipContent>
</Tooltip>
) : null}
<Editor <Editor
value={code} value={code}
@@ -117,6 +143,9 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
contextmenu: false, contextmenu: false,
}} }}
/> />
{!isComplete ? (
<div className="absolute bottom-2 right-2 size-2 animate-blink rounded-full bg-pink-600" />
) : null}
</Suspense> </Suspense>
)} )}
</div> </div>

View File

@@ -3,6 +3,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons'; import { Cross2Icon } from '@radix-ui/react-icons';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ScrollArea } from '../scroll-area/scroll-area';
const Dialog = DialogPrimitive.Root; const Dialog = DialogPrimitive.Root;
@@ -110,6 +111,18 @@ const DialogDescription = React.forwardRef<
)); ));
DialogDescription.displayName = DialogPrimitive.Description.displayName; DialogDescription.displayName = DialogPrimitive.Description.displayName;
const DialogInternalContent = React.forwardRef<
React.ElementRef<typeof ScrollArea>,
React.ComponentPropsWithoutRef<typeof ScrollArea>
>(({ className, ...props }, ref) => (
<ScrollArea
ref={ref}
className={cn('flex max-h-screen flex-col overflow-y-auto', className)}
{...props}
/>
));
DialogInternalContent.displayName = 'DialogInternalContent';
export { export {
Dialog, Dialog,
DialogPortal, DialogPortal,
@@ -121,4 +134,5 @@ export {
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogDescription, DialogDescription,
DialogInternalContent,
}; };

View File

@@ -0,0 +1,168 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Upload, FileIcon, AlertCircle, Trash2 } from 'lucide-react';
import { Button } from '../button/button';
interface FileWithPreview extends File {
preview?: string;
}
export interface FileUploaderProps {
onFilesChange?: (files: File[]) => void;
multiple?: boolean;
supportedExtensions?: string[];
}
export const FileUploader: React.FC<FileUploaderProps> = ({
onFilesChange,
multiple,
supportedExtensions,
}) => {
const [files, setFiles] = useState<FileWithPreview[]>([]);
const [isDragging, setIsDragging] = useState(false);
const [error, setError] = useState<string | null>(null);
const isFileSupported = useCallback(
(file: File) => {
if (!supportedExtensions) return true;
const fileExtension = file.name.split('.').pop()?.toLowerCase();
return fileExtension
? supportedExtensions.includes(`.${fileExtension}`)
: false;
},
[supportedExtensions]
);
const handleFiles = useCallback(
(selectedFiles: FileList) => {
const newFiles = Array.from(selectedFiles)
.filter((file) => {
if (!isFileSupported(file)) {
setError(
`File type not supported. Supported types: ${supportedExtensions?.join(', ')}`
);
return false;
}
return true;
})
.map((file) =>
Object.assign(file, { preview: URL.createObjectURL(file) })
);
if (newFiles.length === 0) return;
setError(null);
setFiles((prevFiles) => {
if (multiple) {
return [...prevFiles, ...newFiles];
} else {
return newFiles.slice(0, 1);
}
});
},
[multiple, supportedExtensions, isFileSupported]
);
const onDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
}, []);
const onDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);
}, []);
const onDrop = useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
handleFiles(e.dataTransfer.files);
}
},
[handleFiles]
);
useEffect(() => {
if (onFilesChange) {
onFilesChange(files.length > 0 ? files : []);
}
}, [files, onFilesChange]);
const removeFile = useCallback((fileToRemove: File) => {
setFiles((prevFiles) =>
prevFiles.filter((file) => file !== fileToRemove)
);
}, []);
return (
<div className="mx-auto w-full max-w-md">
<div
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
className={`cursor-pointer rounded-lg border-2 border-dashed p-8 text-center transition-colors ${
isDragging
? 'border-primary bg-primary/10 dark:bg-primary/20'
: 'border-gray-300 hover:border-primary dark:border-gray-600 dark:hover:border-primary'
}`}
>
<input
type="file"
multiple={multiple}
onChange={(e) =>
e.target.files && handleFiles(e.target.files)
}
className="hidden"
id="fileInput"
accept={supportedExtensions?.join(',')}
/>
<label htmlFor="fileInput" className="cursor-pointer">
<Upload className="mx-auto size-12 text-gray-400 dark:text-gray-500" />
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
{multiple
? 'Drag and drop files here or click to select'
: 'Drag and drop a file here or click to select'}
</p>
{supportedExtensions ? (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Supported types: {supportedExtensions.join(', ')}
</p>
) : null}
</label>
</div>
{error ? (
<div className="mt-4 flex items-center rounded-lg bg-red-100 p-3 text-red-700 dark:bg-red-900 dark:text-red-200">
<AlertCircle className="mr-2 size-5" />
<span className="text-sm">{error}</span>
</div>
) : null}
{files.length > 0 ? (
<ul className="mt-4 space-y-4">
{files.map((file) => (
<li
key={file.name}
className="flex items-center justify-between rounded-lg bg-gray-100 p-3 dark:bg-gray-800"
>
<div className="flex min-w-0 flex-1 items-center space-x-2">
<FileIcon className="size-5 text-primary" />
<span className="truncate text-sm font-medium text-gray-700 dark:text-gray-300">
{file.name}
</span>
</div>
<Button
variant="ghost"
className="size-5 p-0 hover:bg-primary-foreground"
onClick={() => removeFile(file)}
>
<Trash2 className="size-3.5 text-red-700" />
</Button>
</li>
))}
</ul>
) : null}
</div>
);
};

View File

@@ -34,7 +34,7 @@ export const ListMenu = React.forwardRef<HTMLDivElement, ListMenuProps>(
strokeWidth={item.selected ? 2.4 : 2} strokeWidth={item.selected ? 2.4 : 2}
/> />
) : null} ) : null}
{item.title} <span className="min-w-0 truncate">{item.title}</span>
</Link> </Link>
))} ))}
</div> </div>

View File

@@ -12,7 +12,7 @@ const ScrollArea = React.forwardRef<
className={cn('relative overflow-hidden', className)} className={cn('relative overflow-hidden', className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport className="scrollable-flex size-full rounded-[inherit]"> <ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]">
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
@@ -40,7 +40,7 @@ const ScrollBar = React.forwardRef<
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> <ScrollAreaPrimitive.ScrollAreaThumb className="relative z-20 flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
)); ));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;

View File

@@ -26,7 +26,7 @@ export interface SelectBoxOption {
description?: string; description?: string;
} }
interface SelectBoxProps { export interface SelectBoxProps {
options: SelectBoxOption[]; options: SelectBoxOption[];
value?: string[] | string; value?: string[] | string;
onChange?: (values: string[] | string) => void; onChange?: (values: string[] | string) => void;
@@ -156,9 +156,19 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
[options, value, multiple] [options, value, multiple]
); );
const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent) => {
if (!isOpen && e.code.toLowerCase() === 'space') {
e.preventDefault();
onOpenChange(true);
}
},
[isOpen, onOpenChange]
);
return ( return (
<Popover open={isOpen} onOpenChange={onOpenChange} modal={true}> <Popover open={isOpen} onOpenChange={onOpenChange} modal={true}>
<PopoverTrigger asChild> <PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
<div <div
className={cn( className={cn(
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`, `flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`,

View File

@@ -11,8 +11,6 @@ import type { DBRelationship } from '@/lib/domain/db-relationship';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack'; import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { useNavigate } from 'react-router-dom';
import { useConfig } from '@/hooks/use-config';
import type { DatabaseEdition } from '@/lib/domain/database-edition'; import type { DatabaseEdition } from '@/lib/domain/database-edition';
import type { DBSchema } from '@/lib/domain/db-schema'; import type { DBSchema } from '@/lib/domain/db-schema';
import { import {
@@ -34,13 +32,11 @@ export const ChartDBProvider: React.FC<
> = ({ children, diagram, readonly }) => { > = ({ children, diagram, readonly }) => {
let db = useStorage(); let db = useStorage();
const events = useEventEmitter<ChartDBEvent>(); const events = useEventEmitter<ChartDBEvent>();
const navigate = useNavigate();
const { setSchemasFilter, schemasFilter } = useLocalConfig(); const { setSchemasFilter, schemasFilter } = useLocalConfig();
const { addUndoAction, resetRedoStack, resetUndoStack } = const { addUndoAction, resetRedoStack, resetUndoStack } =
useRedoUndoStack(); useRedoUndoStack();
const [diagramId, setDiagramId] = useState(''); const [diagramId, setDiagramId] = useState('');
const [diagramName, setDiagramName] = useState(''); const [diagramName, setDiagramName] = useState('');
const { updateConfig } = useConfig();
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date()); const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
const [diagramUpdatedAt, setDiagramUpdatedAt] = useState<Date>(new Date()); const [diagramUpdatedAt, setDiagramUpdatedAt] = useState<Date>(new Date());
const [databaseType, setDatabaseType] = useState<DatabaseType>( const [databaseType, setDatabaseType] = useState<DatabaseType>(
@@ -173,34 +169,13 @@ export const ChartDBProvider: React.FC<
resetRedoStack(); resetRedoStack();
resetUndoStack(); resetUndoStack();
const [config] = await Promise.all([ await Promise.all([
db.getConfig(),
db.deleteDiagramTables(diagramId), db.deleteDiagramTables(diagramId),
db.deleteDiagramRelationships(diagramId), db.deleteDiagramRelationships(diagramId),
db.deleteDiagram(diagramId), db.deleteDiagram(diagramId),
db.deleteDiagramDependencies(diagramId), db.deleteDiagramDependencies(diagramId),
]); ]);
}, [db, diagramId, resetRedoStack, resetUndoStack]);
if (config?.defaultDiagramId === diagramId) {
const diagrams = await db.listDiagrams();
if (diagrams.length > 0) {
const defaultDiagramId = diagrams[0].id;
await updateConfig({ defaultDiagramId });
navigate(`/diagrams/${defaultDiagramId}`);
} else {
await updateConfig({ defaultDiagramId: '' });
navigate('/');
}
}
}, [
db,
diagramId,
navigate,
resetRedoStack,
resetUndoStack,
updateConfig,
]);
const updateDiagramUpdatedAt: ChartDBContext['updateDiagramUpdatedAt'] = const updateDiagramUpdatedAt: ChartDBContext['updateDiagramUpdatedAt'] =
useCallback(async () => { useCallback(async () => {

View File

@@ -5,6 +5,8 @@ import type { TableSchemaDialogProps } from '@/dialogs/table-schema-dialog/table
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog'; import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog'; import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog'; import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
export interface DialogContext { export interface DialogContext {
// Create diagram dialog // Create diagram dialog
@@ -48,6 +50,18 @@ export interface DialogContext {
params: Omit<ExportImageDialogProps, 'dialog'> params: Omit<ExportImageDialogProps, 'dialog'>
) => void; ) => void;
closeExportImageDialog: () => void; closeExportImageDialog: () => void;
// Export diagram dialog
openExportDiagramDialog: (
params: Omit<ExportDiagramDialogProps, 'dialog'>
) => void;
closeExportDiagramDialog: () => void;
// Import diagram dialog
openImportDiagramDialog: (
params: Omit<ImportDiagramDialogProps, 'dialog'>
) => void;
closeImportDiagramDialog: () => void;
} }
export const dialogContext = createContext<DialogContext>({ export const dialogContext = createContext<DialogContext>({
@@ -69,4 +83,8 @@ export const dialogContext = createContext<DialogContext>({
closeStarUsDialog: emptyFn, closeStarUsDialog: emptyFn,
openExportImageDialog: emptyFn, openExportImageDialog: emptyFn,
closeExportImageDialog: emptyFn, closeExportImageDialog: emptyFn,
openExportDiagramDialog: emptyFn,
closeExportDiagramDialog: emptyFn,
openImportDiagramDialog: emptyFn,
closeImportDiagramDialog: emptyFn,
}); });

View File

@@ -17,6 +17,8 @@ import { emptyFn } from '@/lib/utils';
import { StarUsDialog } from '@/dialogs/star-us-dialog/star-us-dialog'; import { StarUsDialog } from '@/dialogs/star-us-dialog/star-us-dialog';
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog'; import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog'; import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
export const DialogProvider: React.FC<React.PropsWithChildren> = ({ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
children, children,
@@ -86,6 +88,14 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
[setOpenTableSchemaDialog] [setOpenTableSchemaDialog]
); );
// Export image dialog
const [openExportDiagramDialog, setOpenExportDiagramDialog] =
useState(false);
// Import diagram dialog
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
useState(false);
// Alert dialog // Alert dialog
const [showAlert, setShowAlert] = useState(false); const [showAlert, setShowAlert] = useState(false);
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({ const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
@@ -126,6 +136,12 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeStarUsDialog: () => setOpenStarUsDialog(false), closeStarUsDialog: () => setOpenStarUsDialog(false),
closeExportImageDialog: () => setOpenExportImageDialog(false), closeExportImageDialog: () => setOpenExportImageDialog(false),
openExportImageDialog: openExportImageDialogHandler, openExportImageDialog: openExportImageDialogHandler,
openExportDiagramDialog: () => setOpenExportDiagramDialog(true),
closeExportDiagramDialog: () =>
setOpenExportDiagramDialog(false),
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
closeImportDiagramDialog: () =>
setOpenImportDiagramDialog(false),
}} }}
> >
{children} {children}
@@ -152,6 +168,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
dialog={{ open: openExportImageDialog }} dialog={{ open: openExportImageDialog }}
{...exportImageDialogParams} {...exportImageDialogParams}
/> />
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
</dialogContext.Provider> </dialogContext.Provider>
); );
}; };

View File

@@ -5,12 +5,14 @@ import { toJpeg, toPng, toSvg } from 'html-to-image';
import { useReactFlow } from '@xyflow/react'; import { useReactFlow } from '@xyflow/react';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner'; import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
import { useTheme } from '@/hooks/use-theme';
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
children, children,
}) => { }) => {
const { hideLoader, showLoader } = useFullScreenLoader(); const { hideLoader, showLoader } = useFullScreenLoader();
const { setNodes, getViewport } = useReactFlow(); const { setNodes, getViewport } = useReactFlow();
const { effectiveTheme } = useTheme();
const { diagramName } = useChartDB(); const { diagramName } = useChartDB();
const downloadImage = useCallback( const downloadImage = useCallback(
@@ -59,13 +61,101 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
const imageCreateFn = imageCreatorMap[type]; const imageCreateFn = imageCreatorMap[type];
setTimeout(async () => { setTimeout(async () => {
const dataUrl = await imageCreateFn( const viewportElement = window.document.querySelector(
window.document.querySelector( '.react-flow__viewport'
'.react-flow__viewport' ) as HTMLElement;
) as HTMLElement,
{ const markerDefs = document.querySelector(
'.marker-definitions defs'
);
const tempSvg = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
tempSvg.style.position = 'absolute';
tempSvg.style.top = '0';
tempSvg.style.left = '0';
tempSvg.style.width = '100%';
tempSvg.style.height = '100%';
tempSvg.style.overflow = 'visible';
tempSvg.style.zIndex = '-50';
tempSvg.setAttribute(
'viewBox',
`0 0 ${reactFlowBounds.width} ${reactFlowBounds.height}`
);
const defs = document.createElementNS(
'http://www.w3.org/2000/svg',
'defs'
);
if (markerDefs) {
defs.innerHTML = markerDefs.innerHTML;
}
const pattern = document.createElementNS(
'http://www.w3.org/2000/svg',
'pattern'
);
pattern.setAttribute('id', 'background-pattern');
pattern.setAttribute('width', String(16 * viewport.zoom));
pattern.setAttribute('height', String(16 * viewport.zoom));
pattern.setAttribute('patternUnits', 'userSpaceOnUse');
pattern.setAttribute(
'patternTransform',
`translate(${viewport.x % (16 * viewport.zoom)} ${viewport.y % (16 * viewport.zoom)})`
);
const dot = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
const dotSize = viewport.zoom * 0.5;
dot.setAttribute('cx', String(viewport.zoom));
dot.setAttribute('cy', String(viewport.zoom));
dot.setAttribute('r', String(dotSize));
const dotColor =
effectiveTheme === 'light' ? '#92939C' : '#777777';
dot.setAttribute('fill', dotColor);
pattern.appendChild(dot);
defs.appendChild(pattern);
tempSvg.appendChild(defs);
const backgroundRect = document.createElementNS(
'http://www.w3.org/2000/svg',
'rect'
);
const padding = 2000;
backgroundRect.setAttribute('x', String(-viewport.x - padding));
backgroundRect.setAttribute('y', String(-viewport.y - padding));
backgroundRect.setAttribute(
'width',
String(reactFlowBounds.width + 2 * padding)
);
backgroundRect.setAttribute(
'height',
String(reactFlowBounds.height + 2 * padding)
);
backgroundRect.setAttribute('fill', 'url(#background-pattern)');
tempSvg.appendChild(backgroundRect);
viewportElement.insertBefore(
tempSvg,
viewportElement.firstChild
);
try {
const dataUrl = await imageCreateFn(viewportElement, {
...(type === 'jpeg' || type === 'png' ...(type === 'jpeg' || type === 'png'
? { backgroundColor: '#ffffff' } ? {
backgroundColor:
effectiveTheme === 'light'
? '#ffffff'
: '#141414',
}
: {}), : {}),
width: reactFlowBounds.width, width: reactFlowBounds.width,
height: reactFlowBounds.height, height: reactFlowBounds.height,
@@ -76,11 +166,13 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
}, },
quality: 1, quality: 1,
pixelRatio: scale, pixelRatio: scale,
} });
);
downloadImage(dataUrl, type); downloadImage(dataUrl, type);
hideLoader(); } finally {
viewportElement.removeChild(tempSvg);
hideLoader();
}
}, 0); }, 0);
}, },
[ [
@@ -90,6 +182,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
imageCreatorMap, imageCreatorMap,
setNodes, setNodes,
showLoader, showLoader,
effectiveTheme,
] ]
); );

View File

@@ -8,6 +8,7 @@ import {
import { useHistory } from '@/hooks/use-history'; import { useHistory } from '@/hooks/use-history';
import { useDialog } from '@/hooks/use-dialog'; import { useDialog } from '@/hooks/use-dialog';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { useLayout } from '@/hooks/use-layout';
export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
children, children,
@@ -15,6 +16,8 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
const { redo, undo } = useHistory(); const { redo, undo } = useHistory();
const { openOpenDiagramDialog } = useDialog(); const { openOpenDiagramDialog } = useDialog();
const { updateDiagramUpdatedAt } = useChartDB(); const { updateDiagramUpdatedAt } = useChartDB();
const { toggleSidePanel } = useLayout();
useHotkeys( useHotkeys(
keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination, keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
redo, redo,
@@ -49,6 +52,15 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
}, },
[updateDiagramUpdatedAt] [updateDiagramUpdatedAt]
); );
useHotkeys(
keyboardShortcutsForOS[KeyboardShortcutAction.TOGGLE_SIDE_PANEL]
.keyCombination,
toggleSidePanel,
{
preventDefault: true,
},
[toggleSidePanel]
);
return ( return (
<keyboardShortcutsContext.Provider value={{}}> <keyboardShortcutsContext.Provider value={{}}>

View File

@@ -5,6 +5,7 @@ export enum KeyboardShortcutAction {
UNDO = 'undo', UNDO = 'undo',
OPEN_DIAGRAM = 'open_diagram', OPEN_DIAGRAM = 'open_diagram',
SAVE_DIAGRAM = 'save_diagram', SAVE_DIAGRAM = 'save_diagram',
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
} }
export interface KeyboardShortcut { export interface KeyboardShortcut {
@@ -47,6 +48,13 @@ export const keyboardShortcuts: Record<
keyCombinationMac: 'meta+s', keyCombinationMac: 'meta+s',
keyCombinationWin: 'ctrl+s', keyCombinationWin: 'ctrl+s',
}, },
[KeyboardShortcutAction.TOGGLE_SIDE_PANEL]: {
action: KeyboardShortcutAction.TOGGLE_SIDE_PANEL,
keyCombinationLabelMac: '⌘B',
keyCombinationLabelWin: 'Ctrl+B',
keyCombinationMac: 'meta+b',
keyCombinationWin: 'ctrl+b',
},
}; };
export interface KeyboardShortcutForOS { export interface KeyboardShortcutForOS {

View File

@@ -22,6 +22,7 @@ export interface LayoutContext {
isSidePanelShowed: boolean; isSidePanelShowed: boolean;
hideSidePanel: () => void; hideSidePanel: () => void;
showSidePanel: () => void; showSidePanel: () => void;
toggleSidePanel: () => void;
isSelectSchemaOpen: boolean; isSelectSchemaOpen: boolean;
openSelectSchema: () => void; openSelectSchema: () => void;
@@ -47,6 +48,7 @@ export const layoutContext = createContext<LayoutContext>({
isSidePanelShowed: false, isSidePanelShowed: false,
hideSidePanel: emptyFn, hideSidePanel: emptyFn,
showSidePanel: emptyFn, showSidePanel: emptyFn,
toggleSidePanel: emptyFn,
isSelectSchemaOpen: false, isSelectSchemaOpen: false,
openSelectSchema: emptyFn, openSelectSchema: emptyFn,

View File

@@ -36,6 +36,10 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
const showSidePanel: LayoutContext['showSidePanel'] = () => const showSidePanel: LayoutContext['showSidePanel'] = () =>
setIsSidePanelShowed(true); setIsSidePanelShowed(true);
const toggleSidePanel: LayoutContext['toggleSidePanel'] = () => {
setIsSidePanelShowed((prevIsSidePanelShowed) => !prevIsSidePanelShowed);
};
const openTableFromSidebar: LayoutContext['openTableFromSidebar'] = ( const openTableFromSidebar: LayoutContext['openTableFromSidebar'] = (
tableId tableId
) => { ) => {
@@ -77,6 +81,7 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
isSidePanelShowed, isSidePanelShowed,
hideSidePanel, hideSidePanel,
showSidePanel, showSidePanel,
toggleSidePanel,
isSelectSchemaOpen, isSelectSchemaOpen,
openSelectSchema, openSelectSchema,
closeSelectSchema, closeSelectSchema,

View File

@@ -5,6 +5,7 @@ import {
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogInternalContent,
DialogTitle, DialogTitle,
} from '@/components/dialog/dialog'; } from '@/components/dialog/dialog';
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group'; import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
@@ -139,6 +140,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
setScriptResult(fixedJson); setScriptResult(fixedJson);
setErrorMessage(''); setErrorMessage('');
} else { } else {
setScriptResult(fixedJson);
setErrorMessage(errorScriptOutputMessage); setErrorMessage(errorScriptOutputMessage);
} }
@@ -157,188 +159,201 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const renderContent = useCallback(() => { const renderContent = useCallback(() => {
return ( return (
<div className="flex w-full flex-1 flex-col gap-6"> <DialogInternalContent>
{databaseTypeToEditionMap[databaseType].length > 0 ? ( <div className="flex w-full flex-1 flex-col gap-6">
<div className="flex flex-col gap-1 md:flex-row"> {databaseTypeToEditionMap[databaseType].length > 0 ? (
<p className="text-sm leading-6 text-muted-foreground"> <div className="flex flex-col gap-1 md:flex-row">
{t( <p className="text-sm leading-6 text-muted-foreground">
'new_diagram_dialog.import_database.database_edition' {t(
)} 'new_diagram_dialog.import_database.database_edition'
</p> )}
<ToggleGroup </p>
type="single" <ToggleGroup
className="ml-1 flex-wrap gap-2" type="single"
value={ className="ml-1 flex-wrap gap-2"
!databaseEdition ? 'regular' : databaseEdition value={
} !databaseEdition
onValueChange={(value) => { ? 'regular'
setDatabaseEdition( : databaseEdition
value === 'regular' }
? undefined onValueChange={(value) => {
: (value as DatabaseEdition) setDatabaseEdition(
); value === 'regular'
}} ? undefined
> : (value as DatabaseEdition)
<ToggleGroupItem );
value="regular" }}
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
> >
<Avatar className="size-4 rounded-none"> <ToggleGroupItem
<AvatarImage value="regular"
src={ variant="outline"
databaseSecondaryLogoMap[ className="h-6 gap-1 p-0 px-2 shadow-none"
databaseType >
] <Avatar className="size-4 rounded-none">
} <AvatarImage
alt="Regular" src={
/> databaseSecondaryLogoMap[
<AvatarFallback>Regular</AvatarFallback> databaseType
</Avatar> ]
Regular }
</ToggleGroupItem> alt="Regular"
{databaseTypeToEditionMap[databaseType].map( />
(edition) => ( <AvatarFallback>Regular</AvatarFallback>
<ToggleGroupItem </Avatar>
value={edition} Regular
key={edition} </ToggleGroupItem>
variant="outline" {databaseTypeToEditionMap[databaseType].map(
className="h-6 gap-1 p-0 px-2 shadow-none" (edition) => (
> <ToggleGroupItem
<Avatar className="size-4"> value={edition}
<AvatarImage key={edition}
src={ variant="outline"
databaseEditionToImageMap[ className="h-6 gap-1 p-0 px-2 shadow-none"
edition
]
}
alt={
databaseEditionToLabelMap[
edition
]
}
/>
<AvatarFallback>
{
databaseEditionToLabelMap[
edition
]
}
</AvatarFallback>
</Avatar>
{databaseEditionToLabelMap[edition]}
</ToggleGroupItem>
)
)}
</ToggleGroup>
</div>
) : null}
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
<div>
1. {t('new_diagram_dialog.import_database.step_1')}
</div>
{databaseType === DatabaseType.SQL_SERVER && (
<SSMSInfo />
)}
</div>
{databaseTypeToClientsMap[databaseType].length > 0 ? (
<Tabs
value={
!databaseClient ? 'dbclient' : databaseClient
}
onValueChange={(value) => {
setDatabaseClient(
value === 'dbclient'
? undefined
: (value as DatabaseClient)
);
}}
>
<div className="flex flex-1">
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
<TabsTrigger
value="dbclient"
className="h-6 w-20"
>
DB Client
</TabsTrigger>
{databaseClients?.map((client) => (
<TabsTrigger
key={client}
value={client}
className="h-6 !w-20"
> >
{databaseClientToLabelMap[client]} <Avatar className="size-4">
</TabsTrigger> <AvatarImage
)) ?? []} src={
</TabsList> databaseEditionToImageMap[
edition
]
}
alt={
databaseEditionToLabelMap[
edition
]
}
/>
<AvatarFallback>
{
databaseEditionToLabelMap[
edition
]
}
</AvatarFallback>
</Avatar>
{databaseEditionToLabelMap[edition]}
</ToggleGroupItem>
)
)}
</ToggleGroup>
</div>
) : null}
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
<div>
1.{' '}
{t('new_diagram_dialog.import_database.step_1')}
</div> </div>
{databaseType === DatabaseType.SQL_SERVER && (
<SSMSInfo />
)}
</div>
{databaseTypeToClientsMap[databaseType].length > 0 ? (
<Tabs
value={
!databaseClient
? 'dbclient'
: databaseClient
}
onValueChange={(value) => {
setDatabaseClient(
value === 'dbclient'
? undefined
: (value as DatabaseClient)
);
}}
>
<div className="flex flex-1">
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
<TabsTrigger
value="dbclient"
className="h-6 w-20"
>
DB Client
</TabsTrigger>
{databaseClients?.map((client) => (
<TabsTrigger
key={client}
value={client}
className="h-6 !w-20"
>
{
databaseClientToLabelMap[
client
]
}
</TabsTrigger>
)) ?? []}
</TabsList>
</div>
<CodeSnippet
className="h-40 w-full"
loading={!importMetadataScripts}
code={
importMetadataScripts?.[databaseType]?.(
{
databaseEdition,
databaseClient,
}
) ?? ''
}
language={databaseClient ? 'shell' : 'sql'}
/>
</Tabs>
) : (
<CodeSnippet <CodeSnippet
className="h-40 w-full" className="h-40 w-full flex-auto"
loading={!importMetadataScripts} loading={!importMetadataScripts}
code={ code={
importMetadataScripts?.[databaseType]?.({ importMetadataScripts?.[databaseType]?.({
databaseEdition, databaseEdition,
databaseClient,
}) ?? '' }) ?? ''
} }
language={databaseClient ? 'shell' : 'sql'} language="sql"
/> />
</Tabs>
) : (
<CodeSnippet
className="h-40 w-full flex-auto"
loading={!importMetadataScripts}
code={
importMetadataScripts?.[databaseType]?.({
databaseEdition,
}) ?? ''
}
language="sql"
/>
)}
</div>
<div className="flex h-48 flex-col gap-1">
<p className="text-sm text-muted-foreground">
2. {t('new_diagram_dialog.import_database.step_2')}
</p>
<Textarea
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
placeholder={t(
'new_diagram_dialog.import_database.script_results_placeholder'
)} )}
value={scriptResult} </div>
onChange={handleInputChange} <div className="flex h-48 flex-col gap-1">
/> <p className="text-sm text-muted-foreground">
{showCheckJsonButton || errorMessage ? ( 2. {t('new_diagram_dialog.import_database.step_2')}
<div className="mt-2 flex items-center gap-2"> </p>
{showCheckJsonButton ? ( <Textarea
<Button className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
type="button" placeholder={t(
variant="outline" 'new_diagram_dialog.import_database.script_results_placeholder'
size="sm"
onClick={handleCheckJson}
disabled={isCheckingJson}
>
{isCheckingJson ? (
<Spinner size="small" />
) : (
t(
'new_diagram_dialog.import_database.check_script_result'
)
)}
</Button>
) : (
<p className="text-sm text-red-700">
{errorMessage}
</p>
)} )}
</div> value={scriptResult}
) : null} onChange={handleInputChange}
/>
{showCheckJsonButton || errorMessage ? (
<div className="mt-2 flex items-center gap-2">
{showCheckJsonButton ? (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleCheckJson}
disabled={isCheckingJson}
>
{isCheckingJson ? (
<Spinner size="small" />
) : (
t(
'new_diagram_dialog.import_database.check_script_result'
)
)}
</Button>
) : (
<p className="text-sm text-red-700">
{errorMessage}
</p>
)}
</div>
) : null}
</div>
</div> </div>
</div> </DialogInternalContent>
); );
}, [ }, [
databaseEdition, databaseEdition,

View File

@@ -128,7 +128,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
}} }}
> >
<DialogContent <DialogContent
className="flex w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]" className="flex max-h-screen w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]"
showClose={hasExistingDiagram} showClose={hasExistingDiagram}
> >
{step === CreateDiagramDialogStep.SELECT_DATABASE ? ( {step === CreateDiagramDialogStep.SELECT_DATABASE ? (

View File

@@ -5,11 +5,13 @@ import {
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogInternalContent,
DialogTitle, DialogTitle,
} from '@/components/dialog/dialog'; } from '@/components/dialog/dialog';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SelectDatabaseContent } from './select-database-content'; import { SelectDatabaseContent } from './select-database-content';
import { useDialog } from '@/hooks/use-dialog';
export interface SelectDatabaseProps { export interface SelectDatabaseProps {
onContinue: () => void; onContinue: () => void;
@@ -27,6 +29,7 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
createNewDiagram, createNewDiagram,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { openImportDiagramDialog } = useDialog();
return ( return (
<> <>
@@ -38,11 +41,13 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
{t('new_diagram_dialog.database_selection.description')} {t('new_diagram_dialog.database_selection.description')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<SelectDatabaseContent <DialogInternalContent>
databaseType={databaseType} <SelectDatabaseContent
onContinue={onContinue} databaseType={databaseType}
setDatabaseType={setDatabaseType} onContinue={onContinue}
/> setDatabaseType={setDatabaseType}
/>
</DialogInternalContent>
<DialogFooter className="mt-4 flex !justify-between gap-2"> <DialogFooter className="mt-4 flex !justify-between gap-2">
{hasExistingDiagram ? ( {hasExistingDiagram ? (
<DialogClose asChild> <DialogClose asChild>
@@ -51,7 +56,13 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
</Button> </Button>
</DialogClose> </DialogClose>
) : ( ) : (
<div></div> <Button
type="button"
variant="ghost"
onClick={openImportDiagramDialog}
>
{t('new_diagram_dialog.import_from_file')}
</Button>
)} )}
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2"> <div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2">
<Button <Button

View File

@@ -0,0 +1,132 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDialog } from '@/hooks/use-dialog';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/dialog/dialog';
import { Button } from '@/components/button/button';
import type { SelectBoxOption } from '@/components/select-box/select-box';
import { SelectBox } from '@/components/select-box/select-box';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useTranslation } from 'react-i18next';
import { useChartDB } from '@/hooks/use-chartdb';
import { diagramToJSONOutput } from '@/lib/export-import-utils';
import { Spinner } from '@/components/spinner/spinner';
import { waitFor } from '@/lib/utils';
import { AlertCircle } from 'lucide-react';
import { Alert, AlertDescription, AlertTitle } from '@/components/alert/alert';
export interface ExportDiagramDialogProps extends BaseDialogProps {}
export const ExportDiagramDialog: React.FC<ExportDiagramDialogProps> = ({
dialog,
}) => {
const { t } = useTranslation();
const { diagramName, currentDiagram } = useChartDB();
const [isLoading, setIsLoading] = useState(false);
const { closeExportDiagramDialog } = useDialog();
const [error, setError] = useState(false);
useEffect(() => {
if (!dialog.open) return;
setIsLoading(false);
setError(false);
}, [dialog.open]);
const downloadOutput = useCallback(
(dataUrl: string) => {
const a = document.createElement('a');
a.setAttribute('download', `ChartDB(${diagramName}).json`);
a.setAttribute('href', dataUrl);
a.click();
},
[diagramName]
);
const handleExport = useCallback(async () => {
setIsLoading(true);
await waitFor(1000);
try {
const json = diagramToJSONOutput(currentDiagram);
const blob = new Blob([json], { type: 'application/json' });
const dataUrl = URL.createObjectURL(blob);
downloadOutput(dataUrl);
setIsLoading(false);
closeExportDiagramDialog();
} catch (e) {
setError(true);
setIsLoading(false);
throw e;
}
}, [downloadOutput, currentDiagram, closeExportDiagramDialog]);
const outputTypeOptions: SelectBoxOption[] = useMemo(
() =>
['json'].map((format) => ({
value: format,
label: t(`export_diagram_dialog.format_${format}`),
})),
[t]
);
return (
<Dialog
{...dialog}
onOpenChange={(open) => {
if (!open) {
closeExportDiagramDialog();
}
}}
>
<DialogContent className="flex flex-col" showClose>
<DialogHeader>
<DialogTitle>
{t('export_diagram_dialog.title')}
</DialogTitle>
<DialogDescription>
{t('export_diagram_dialog.description')}
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-1">
<div className="grid w-full items-center gap-4">
<SelectBox
options={outputTypeOptions}
multiple={false}
value="json"
/>
</div>
{error ? (
<Alert variant="destructive">
<AlertCircle className="size-4" />
<AlertTitle>
{t('export_diagram_dialog.error.title')}
</AlertTitle>
<AlertDescription>
{t('export_diagram_dialog.error.description')}
</AlertDescription>
</Alert>
) : null}
</div>
<DialogFooter className="flex gap-1 md:justify-between">
<DialogClose asChild>
<Button variant="secondary">
{t('export_diagram_dialog.cancel')}
</Button>
</DialogClose>
<Button onClick={handleExport} disabled={isLoading}>
{isLoading ? (
<Spinner className="mr-1 size-5 text-primary-foreground" />
) : null}
{t('export_diagram_dialog.export')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -7,6 +7,7 @@ import {
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogInternalContent,
DialogTitle, DialogTitle,
} from '@/components/dialog/dialog'; } from '@/components/dialog/dialog';
import { Label } from '@/components/label/label'; import { Label } from '@/components/label/label';
@@ -20,7 +21,7 @@ import {
import { databaseTypeToLabelMap } from '@/lib/databases'; import { databaseTypeToLabelMap } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { Annoyed, Sparkles } from 'lucide-react'; import { Annoyed, Sparkles } from 'lucide-react';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
@@ -37,28 +38,47 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const { t } = useTranslation(); const { t } = useTranslation();
const [script, setScript] = React.useState<string>(); const [script, setScript] = React.useState<string>();
const [error, setError] = React.useState<boolean>(false); const [error, setError] = React.useState<boolean>(false);
const [isScriptLoading, setIsScriptLoading] =
React.useState<boolean>(false);
const abortControllerRef = useRef<AbortController | null>(null);
const exportSQLScript = useCallback(async () => { const exportSQLScript = useCallback(async () => {
if (targetDatabaseType === DatabaseType.GENERIC) { if (targetDatabaseType === DatabaseType.GENERIC) {
return Promise.resolve(exportBaseSQL(currentDiagram)); return Promise.resolve(exportBaseSQL(currentDiagram));
} else { } else {
return exportSQL(currentDiagram, targetDatabaseType); return exportSQL(currentDiagram, targetDatabaseType, {
stream: true,
onResultStream: (text) =>
setScript((prev) => (prev ? prev + text : text)),
signal: abortControllerRef.current?.signal,
});
} }
}, [targetDatabaseType, currentDiagram]); }, [targetDatabaseType, currentDiagram]);
useEffect(() => { useEffect(() => {
if (!dialog.open) return; if (!dialog.open) {
abortControllerRef.current?.abort();
return;
}
abortControllerRef.current = new AbortController();
setScript(undefined); setScript(undefined);
setError(false); setError(false);
const fetchScript = async () => { const fetchScript = async () => {
try { try {
setIsScriptLoading(true);
const script = await exportSQLScript(); const script = await exportSQLScript();
setScript(script); setScript(script);
setIsScriptLoading(false);
} catch (e) { } catch (e) {
setError(true); setError(true);
} }
}; };
fetchScript(); fetchScript();
return () => {
abortControllerRef.current?.abort();
};
}, [dialog.open, setScript, exportSQLScript, setError]); }, [dialog.open, setScript, exportSQLScript, setError]);
const renderError = useCallback( const renderError = useCallback(
@@ -132,7 +152,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
}} }}
> >
<DialogContent <DialogContent
className="flex max-h-[80vh] flex-col overflow-y-auto xl:min-w-[75vw]" className="flex max-h-screen flex-col overflow-y-auto xl:min-w-[75vw]"
showClose showClose
> >
<DialogHeader> <DialogHeader>
@@ -148,18 +168,24 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
})} })}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex flex-1 items-center justify-center"> <DialogInternalContent>
{error ? ( <div className="flex flex-1 items-center justify-center">
renderError() {error ? (
) : script === undefined ? ( renderError()
renderLoader() ) : script === undefined ? (
) : script.length === 0 ? ( renderLoader()
renderError() ) : script.length === 0 ? (
) : ( renderError()
<CodeSnippet className="h-96 w-full" code={script!} /> ) : (
)} <CodeSnippet
</div> className="h-96 w-full"
code={script!}
autoScroll={true}
isComplete={!isScriptLoading}
/>
)}
</div>
</DialogInternalContent>
<DialogFooter className="flex !justify-between gap-2"> <DialogFooter className="flex !justify-between gap-2">
<div /> <div />
<DialogClose asChild> <DialogClose asChild>

View File

@@ -323,7 +323,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
}} }}
> >
<DialogContent <DialogContent
className="flex w-[90vw] flex-col overflow-y-auto md:overflow-visible xl:min-w-[45vw]" className="flex max-h-screen w-[90vw] flex-col overflow-y-auto md:overflow-visible xl:min-w-[45vw]"
showClose showClose
> >
<ImportDatabase <ImportDatabase

View File

@@ -0,0 +1,134 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useDialog } from '@/hooks/use-dialog';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { Button } from '@/components/button/button';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useTranslation } from 'react-i18next';
import { FileUploader } from '@/components/file-uploader/file-uploader';
import { useStorage } from '@/hooks/use-storage';
import { useNavigate } from 'react-router-dom';
import { diagramFromJSONInput } from '@/lib/export-import-utils';
import { Alert, AlertDescription, AlertTitle } from '@/components/alert/alert';
import { AlertCircle } from 'lucide-react';
export interface ImportDiagramDialogProps extends BaseDialogProps {}
export const ImportDiagramDialog: React.FC<ImportDiagramDialogProps> = ({
dialog,
}) => {
const { t } = useTranslation();
const [file, setFile] = useState<File | null>(null);
const { addDiagram } = useStorage();
const navigate = useNavigate();
const [error, setError] = useState(false);
const onFileChange = useCallback((files: File[]) => {
if (files.length === 0) {
setFile(null);
return;
}
setFile(files[0]);
}, []);
useEffect(() => {
if (!dialog.open) return;
setError(false);
setFile(null);
}, [dialog.open]);
const { closeImportDiagramDialog, closeCreateDiagramDialog } = useDialog();
const handleImport = useCallback(() => {
if (!file) return;
const reader = new FileReader();
reader.onload = async (e) => {
const json = e.target?.result;
if (typeof json !== 'string') return;
try {
const diagram = diagramFromJSONInput(json);
await addDiagram({ diagram });
closeImportDiagramDialog();
closeCreateDiagramDialog();
navigate(`/diagrams/${diagram.id}`);
} catch (e) {
setError(true);
throw e;
}
};
reader.readAsText(file);
}, [
file,
addDiagram,
navigate,
closeImportDiagramDialog,
closeCreateDiagramDialog,
]);
return (
<Dialog
{...dialog}
onOpenChange={(open) => {
if (!open) {
closeImportDiagramDialog();
}
}}
>
<DialogContent className="flex max-h-screen flex-col" showClose>
<DialogHeader>
<DialogTitle>
{t('import_diagram_dialog.title')}
</DialogTitle>
<DialogDescription>
{t('import_diagram_dialog.description')}
</DialogDescription>
</DialogHeader>
<DialogInternalContent>
<div className="flex flex-col p-1">
<FileUploader
supportedExtensions={['.json']}
onFilesChange={onFileChange}
/>
{error ? (
<Alert variant="destructive" className="mt-2">
<AlertCircle className="size-4" />
<AlertTitle>
{t('import_diagram_dialog.error.title')}
</AlertTitle>
<AlertDescription>
{t(
'import_diagram_dialog.error.description'
)}
</AlertDescription>
</Alert>
) : null}
</div>
</DialogInternalContent>
<DialogFooter className="flex gap-1 md:justify-between">
<DialogClose asChild>
<Button variant="secondary">
{t('import_diagram_dialog.cancel')}
</Button>
</DialogClose>
<Button onClick={handleImport} disabled={file === null}>
{t('import_diagram_dialog.import')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -7,9 +7,9 @@ import {
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogInternalContent,
DialogTitle, DialogTitle,
} from '@/components/dialog/dialog'; } from '@/components/dialog/dialog';
import { ScrollArea } from '@/components/scroll-area/scroll-area';
import { import {
Table, Table,
TableBody, TableBody,
@@ -74,7 +74,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
}} }}
> >
<DialogContent <DialogContent
className="flex w-[90vw] flex-col overflow-y-auto md:w-screen xl:min-w-[55vw]" className="flex h-[30rem] max-h-screen w-[90vw] flex-col overflow-y-auto md:w-screen xl:min-w-[55vw]"
showClose showClose
> >
<DialogHeader> <DialogHeader>
@@ -83,9 +83,9 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
{t('open_diagram_dialog.description')} {t('open_diagram_dialog.description')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex flex-1 items-center justify-center"> <DialogInternalContent>
<ScrollArea className="h-80 w-full"> <div className="flex flex-1 items-center justify-center">
<Table className="h-fit"> <Table>
<TableHeader className="sticky top-0 bg-background"> <TableHeader className="sticky top-0 bg-background">
<TableRow> <TableRow>
<TableHead /> <TableHead />
@@ -155,8 +155,8 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
))} ))}
</TableBody> </TableBody>
</Table> </Table>
</ScrollArea> </div>
</div> </DialogInternalContent>
<DialogFooter className="flex !justify-between gap-2"> <DialogFooter className="flex !justify-between gap-2">
<DialogClose asChild> <DialogClose asChild>

View File

@@ -3,69 +3,73 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 222.2 84% 4.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%; --primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%; --primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%; --secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%; --muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%; --muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%; --accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%; --border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%; --ring: 222.2 84% 4.9%;
--radius: 0.5rem; --radius: 0.5rem;
--chart-1: 12 76% 61%; --chart-1: 12 76% 61%;
--chart-2: 173 58% 39%; --chart-2: 173 58% 39%;
--chart-3: 197 37% 24%; --chart-3: 197 37% 24%;
--chart-4: 43 74% 66%; --chart-4: 43 74% 66%;
--chart-5: 27 87% 67%; --chart-5: 27 87% 67%;
--subtitle: 215.3 19.3% 34.5%; --subtitle: 215.3 19.3% 34.5%;
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--card: 222.2 84% 4.9%; --card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%; --popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%; --popover-foreground: 210 40% 98%;
--primary: 210 40% 98%; --primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%; --primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%; --secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%; --secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%; --muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%; --muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%; --accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%; --accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%; --border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%; --ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%; --chart-1: 220 70% 50%;
--chart-2: 160 60% 45%; --chart-2: 160 60% 45%;
--chart-3: 30 80% 55%; --chart-3: 30 80% 55%;
--chart-4: 280 65% 60%; --chart-4: 280 65% 60%;
--chart-5: 340 75% 55%; --chart-5: 340 75% 55%;
--subtitle: 212.7 26.8% 83.9%; --subtitle: 212.7 26.8% 83.9%;
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
}
.text-editable {
@apply dark:group-hover:bg-slate-900 group-hover:bg-slate-100 group-hover:ring-[0.5px] rounded-md cursor-pointer;
}
}

View File

@@ -18,7 +18,7 @@ export const HelmetData: React.FC = () => (
/> />
<meta <meta
property="og:image" property="og:image"
content="https://app.chartdb.io/ChartDB.png" content="https://app.chartdb.io/chartdb.png"
/> />
<meta property="og:url" content="https://app.chartdb.io" /> <meta property="og:url" content="https://app.chartdb.io" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
@@ -32,7 +32,7 @@ export const HelmetData: React.FC = () => (
/> />
<meta <meta
name="twitter:image" name="twitter:image"
content="https://github.com/chartdb/chartdb/raw/main/public/ChartDB.png" content="https://github.com/chartdb/chartdb/raw/main/public/chartdb.png"
/> />
<title>ChartDB - Database schema diagrams visualizer</title> <title>ChartDB - Database schema diagrams visualizer</title>
</Helmet> </Helmet>

View File

@@ -1,12 +1,48 @@
import i18n from 'i18next'; import i18n from 'i18next';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import type { LanguageMetadata } from './types';
import { en, enMetadata } from './locales/en'; import { en, enMetadata } from './locales/en';
import { es } from './locales/es'; import { es, esMetadata } from './locales/es';
import { fr } from './locales/fr'; import { fr, frMetadata } from './locales/fr';
import { de } from './locales/de'; import { de, deMetadata } from './locales/de';
import { hi } from './locales/hi'; import { hi, hiMetadata } from './locales/hi';
import { ja } from './locales/ja'; import { ja, jaMetadata } from './locales/ja';
import { pt_BR } from './locales/pt_BR'; import { ko_KR, ko_KRMetadata } from './locales/ko_KR';
import { pt_BR, pt_BRMetadata } from './locales/pt_BR';
import { uk, ukMetadata } from './locales/uk';
import { ru, ruMetadata } from './locales/ru';
import { zh_CN, zh_CNMetadata } from './locales/zh_CN';
import { zh_TW, zh_TWMetadata } from './locales/zh_TW';
import { ne, neMetadata } from './locales/ne';
import { mr, mrMetadata } from './locales/mr';
import { tr, trMetadata } from './locales/tr';
import { id_ID, id_IDMetadata } from './locales/id_ID';
import { te, teMetadata } from './locales/te';
import { gu, guMetadata } from './locales/gu';
import { vi, viMetadata } from './locales/vi';
export const languages: LanguageMetadata[] = [
enMetadata,
esMetadata,
frMetadata,
deMetadata,
hiMetadata,
jaMetadata,
ko_KRMetadata,
pt_BRMetadata,
ukMetadata,
ruMetadata,
zh_CNMetadata,
zh_TWMetadata,
neMetadata,
mrMetadata,
trMetadata,
id_IDMetadata,
teMetadata,
guMetadata,
viMetadata,
];
const resources = { const resources = {
en, en,
@@ -15,17 +51,30 @@ const resources = {
de, de,
hi, hi,
ja, ja,
ko_KR,
pt_BR, pt_BR,
uk,
ru,
zh_CN,
zh_TW,
ne,
mr,
tr,
id_ID,
te,
gu,
vi,
}; };
i18n.use(initReactI18next).init({ i18n.use(LanguageDetector)
resources, .use(initReactI18next)
lng: enMetadata.code, .init({
interpolation: { resources,
escapeValue: false, interpolation: {
}, escapeValue: false,
fallbackLng: enMetadata.code, },
debug: false, fallbackLng: enMetadata.code,
}); debug: false,
});
export { i18n }; export { i18n };

View File

@@ -28,10 +28,15 @@ export const de: LanguageTranslation = {
show_cardinality: 'Kardinalität anzeigen', show_cardinality: 'Kardinalität anzeigen',
zoom_on_scroll: 'Zoom beim Scrollen', zoom_on_scroll: 'Zoom beim Scrollen',
theme: 'Stil', theme: 'Stil',
change_language: 'Sprache',
show_dependencies: 'Abhängigkeiten anzeigen', show_dependencies: 'Abhängigkeiten anzeigen',
hide_dependencies: 'Abhängigkeiten ausblenden', hide_dependencies: 'Abhängigkeiten ausblenden',
}, },
// TODO: Translate
share: {
share: 'Share',
export_diagram: 'Export Diagram',
import_diagram: 'Import Diagram',
},
help: { help: {
help: 'Hilfe', help: 'Hilfe',
visit_website: 'ChartDB Webseite', visit_website: 'ChartDB Webseite',
@@ -139,6 +144,7 @@ export const de: LanguageTranslation = {
change_schema: 'Schema ändern', change_schema: 'Schema ändern',
add_field: 'Feld hinzufügen', add_field: 'Feld hinzufügen',
add_index: 'Index hinzufügen', add_index: 'Index hinzufügen',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Tabelle löschen', delete_table: 'Tabelle löschen',
}, },
}, },
@@ -226,6 +232,8 @@ export const de: LanguageTranslation = {
cancel: 'Abbrechen', cancel: 'Abbrechen',
back: 'Zurück', back: 'Zurück',
// TODO: Translate
import_from_file: 'Import from File',
empty_diagram: 'Leeres Diagramm', empty_diagram: 'Leeres Diagramm',
continue: 'Weiter', continue: 'Weiter',
import: 'Importieren', import: 'Importieren',
@@ -329,7 +337,31 @@ export const de: LanguageTranslation = {
close: 'Nicht jetzt', close: 'Nicht jetzt',
confirm: 'Natürlich!', confirm: 'Natürlich!',
}, },
// TODO: Translate
export_diagram_dialog: {
title: 'Export Diagram',
description: 'Choose the format for export:',
format_json: 'JSON',
cancel: 'Cancel',
export: 'Export',
error: {
title: 'Error exporting diagram',
description:
'Something went wrong. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_diagram_dialog: {
title: 'Import Diagram',
description: 'Paste the diagram JSON below:',
cancel: 'Cancel',
import: 'Import',
error: {
title: 'Error importing diagram',
description:
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
relationship_type: { relationship_type: {
one_to_one: 'Ein zu Eins (1:1)', one_to_one: 'Ein zu Eins (1:1)',
one_to_many: 'Ein zu Viele (1:n)', one_to_many: 'Ein zu Viele (1:n)',
@@ -344,12 +376,25 @@ export const de: LanguageTranslation = {
table_node_context_menu: { table_node_context_menu: {
edit_table: 'Tabelle bearbeiten', edit_table: 'Tabelle bearbeiten',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Tabelle löschen', delete_table: 'Tabelle löschen',
}, },
// TODO: Add translations
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
tool_tips: {
double_click_to_edit: 'Doppelklicken zum Bearbeiten',
},
language_select: {
change_language: 'Sprache',
},
}, },
}; };
export const deMetadata: LanguageMetadata = { export const deMetadata: LanguageMetadata = {
name: 'Deutsch', name: 'German',
nativeName: 'Deutsch',
code: 'de', code: 'de',
}; };

View File

@@ -28,10 +28,14 @@ export const en = {
show_cardinality: 'Show Cardinality', show_cardinality: 'Show Cardinality',
zoom_on_scroll: 'Zoom on Scroll', zoom_on_scroll: 'Zoom on Scroll',
theme: 'Theme', theme: 'Theme',
change_language: 'Language',
show_dependencies: 'Show Dependencies', show_dependencies: 'Show Dependencies',
hide_dependencies: 'Hide Dependencies', hide_dependencies: 'Hide Dependencies',
}, },
share: {
share: 'Share',
export_diagram: 'Export Diagram',
import_diagram: 'Import Diagram',
},
help: { help: {
help: 'Help', help: 'Help',
visit_website: 'Visit ChartDB', visit_website: 'Visit ChartDB',
@@ -139,6 +143,7 @@ export const en = {
change_schema: 'Change Schema', change_schema: 'Change Schema',
add_field: 'Add Field', add_field: 'Add Field',
add_index: 'Add Index', add_index: 'Add Index',
duplicate_table: 'Duplicate Table',
delete_table: 'Delete Table', delete_table: 'Delete Table',
}, },
}, },
@@ -224,6 +229,7 @@ export const en = {
}, },
cancel: 'Cancel', cancel: 'Cancel',
import_from_file: 'Import from File',
back: 'Back', back: 'Back',
empty_diagram: 'Empty diagram', empty_diagram: 'Empty diagram',
continue: 'Continue', continue: 'Continue',
@@ -328,7 +334,30 @@ export const en = {
close: 'Not now', close: 'Not now',
confirm: 'Of course!', confirm: 'Of course!',
}, },
export_diagram_dialog: {
title: 'Export Diagram',
description: 'Choose the format for export:',
format_json: 'JSON',
cancel: 'Cancel',
export: 'Export',
error: {
title: 'Error exporting diagram',
description:
'Something went wrong. Need help? chartdb.io@gmail.com',
},
},
import_diagram_dialog: {
title: 'Import Diagram',
description: 'Paste the diagram JSON below:',
cancel: 'Cancel',
import: 'Import',
error: {
title: 'Error importing diagram',
description:
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
relationship_type: { relationship_type: {
one_to_one: 'One to One', one_to_one: 'One to One',
one_to_many: 'One to Many', one_to_many: 'One to Many',
@@ -343,12 +372,24 @@ export const en = {
table_node_context_menu: { table_node_context_menu: {
edit_table: 'Edit Table', edit_table: 'Edit Table',
duplicate_table: 'Duplicate Table',
delete_table: 'Delete Table', delete_table: 'Delete Table',
}, },
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
tool_tips: {
double_click_to_edit: 'Double-click to edit',
},
language_select: {
change_language: 'Language',
},
}, },
}; };
export const enMetadata: LanguageMetadata = { export const enMetadata: LanguageMetadata = {
name: 'English', name: 'English',
nativeName: 'English',
code: 'en', code: 'en',
}; };

View File

@@ -28,10 +28,14 @@ export const es: LanguageTranslation = {
hide_sidebar: 'Ocultar Barra Lateral', hide_sidebar: 'Ocultar Barra Lateral',
zoom_on_scroll: 'Zoom al Desplazarse', zoom_on_scroll: 'Zoom al Desplazarse',
theme: 'Tema', theme: 'Tema',
change_language: 'Idioma', show_dependencies: 'Mostrar dependencias',
// TODO: Translate hide_dependencies: 'Ocultar dependencias',
show_dependencies: 'Show Dependencies', },
hide_dependencies: 'Hide Dependencies', // TODO: Translate
share: {
share: 'Share',
export_diagram: 'Export Diagram',
import_diagram: 'Import Diagram',
}, },
help: { help: {
help: 'Ayuda', help: 'Ayuda',
@@ -80,20 +84,19 @@ export const es: LanguageTranslation = {
saved: 'Guardado', saved: 'Guardado',
diagrams: 'Diagramas', diagrams: 'Diagramas',
loading_diagram: 'Cargando diagrama...', loading_diagram: 'Cargando diagrama...',
deselect_all: 'Deselect All', // TODO: Translate deselect_all: 'Deseleccionar todo',
select_all: 'Select All', // TODO: Translate select_all: 'Seleccionar todo',
clear: 'Clear', // TODO: Translate clear: 'Limpiar',
show_more: 'Show More', // TODO: Translate show_more: 'Mostrar más',
show_less: 'Show Less', // TODO: Translate show_less: 'Mostrar menos',
// TODO: Translate
copy_to_clipboard: 'Copy to Clipboard', copy_to_clipboard: 'Copy to Clipboard',
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'Schema:', // TODO: Translate schema: 'Esquema:',
filter_by_schema: 'Filter by schema', // TODO: Translate filter_by_schema: 'Filtrar por esquema',
search_schema: 'Search schema...', // TODO: Translate search_schema: 'Buscar esquema...',
no_schemas_found: 'No schemas found.', // TODO: Translate no_schemas_found: 'No se encontraron esquemas.',
view_all_options: 'Ver todas las opciones...', view_all_options: 'Ver todas las opciones...',
tables_section: { tables_section: {
tables: 'Tablas', tables: 'Tablas',
@@ -113,7 +116,7 @@ export const es: LanguageTranslation = {
index_select_fields: 'Seleccionar campos', index_select_fields: 'Seleccionar campos',
field_name: 'Nombre', field_name: 'Nombre',
field_type: 'Tipo', field_type: 'Tipo',
no_types_found: 'No types found', // TODO: Translate no_types_found: 'No se encontraron tipos',
field_actions: { field_actions: {
title: 'Atributos del Campo', title: 'Atributos del Campo',
unique: 'Único', unique: 'Único',
@@ -132,6 +135,7 @@ export const es: LanguageTranslation = {
change_schema: 'Cambiar Esquema', change_schema: 'Cambiar Esquema',
add_field: 'Agregar Campo', add_field: 'Agregar Campo',
add_index: 'Agregar Índice', add_index: 'Agregar Índice',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Eliminar Tabla', delete_table: 'Eliminar Tabla',
}, },
}, },
@@ -160,23 +164,22 @@ export const es: LanguageTranslation = {
description: 'Crea una relación para conectar tablas', description: 'Crea una relación para conectar tablas',
}, },
}, },
// TODO: Translate
dependencies_section: { dependencies_section: {
dependencies: 'Dependencies', dependencies: 'Dependencias',
filter: 'Filter', filter: 'Filtro',
collapse: 'Collapse All', collapse: 'Colapsar todo',
dependency: { dependency: {
table: 'Table', table: 'Tabla',
dependent_table: 'Dependent View', dependent_table: 'Vista dependiente',
delete_dependency: 'Delete', delete_dependency: 'Eliminar',
dependency_actions: { dependency_actions: {
title: 'Actions', title: 'Acciones',
delete_dependency: 'Delete', delete_dependency: 'Eliminar',
}, },
}, },
empty_state: { empty_state: {
title: 'No dependencies', title: 'Sin dependencias',
description: 'Create a view to get started', description: 'Crea una vista para comenzar',
}, },
}, },
}, },
@@ -189,8 +192,7 @@ export const es: LanguageTranslation = {
undo: 'Deshacer', undo: 'Deshacer',
redo: 'Rehacer', redo: 'Rehacer',
reorder_diagram: 'Reordenar Diagrama', reorder_diagram: 'Reordenar Diagrama',
// TODO: Translate highlight_overlapping_tables: 'Resaltar tablas superpuestas',
highlight_overlapping_tables: 'Highlight Overlapping Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -214,20 +216,20 @@ export const es: LanguageTranslation = {
step_1: 'Ve a Herramientas > Opciones > Resultados de Consulta > SQL Server.', step_1: 'Ve a Herramientas > Opciones > Resultados de Consulta > SQL Server.',
step_2: 'Si estás usando "Resultados en Cuadrícula", cambia el Máximo de Caracteres Recuperados para Datos No XML (configúralo en 9999999).', step_2: 'Si estás usando "Resultados en Cuadrícula", cambia el Máximo de Caracteres Recuperados para Datos No XML (configúralo en 9999999).',
}, },
// TODO: Translate instructions_link: '¿Necesitas ayuda? mira cómo',
instructions_link: 'Need help? Watch how', check_script_result: 'Revisa el resultado del script',
check_script_result: 'Check Script Result',
}, },
cancel: 'Cancelar', cancel: 'Cancelar',
back: 'Atrás', back: 'Atrás',
// TODO: Translate
import_from_file: 'Import from File',
empty_diagram: 'Diagrama vacío', empty_diagram: 'Diagrama vacío',
continue: 'Continuar', continue: 'Continuar',
import: 'Importar', import: 'Importar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
// TODO: Translate
title: 'Abrir Diagrama', title: 'Abrir Diagrama',
description: description:
'Selecciona un diagrama para abrir de la lista a continuación.', 'Selecciona un diagrama para abrir de la lista a continuación.',
@@ -293,16 +295,15 @@ export const es: LanguageTranslation = {
}, },
}, },
// TODO: Translate
export_image_dialog: { export_image_dialog: {
title: 'Export Image', title: 'Exportar imagen',
description: 'Choose the scale factor for export:', description: 'Escoge el factor de escalamiento para exportar:',
scale_1x: '1x Regular', scale_1x: '1x regular',
scale_2x: '2x (Recommended)', scale_2x: '2x (recomendado)',
scale_3x: '3x', scale_3x: '3x',
scale_4x: '4x', scale_4x: '4x',
cancel: 'Cancel', cancel: 'Cancelar',
export: 'Export', export: 'Exportar',
}, },
new_table_schema_dialog: { new_table_schema_dialog: {
@@ -336,7 +337,31 @@ export const es: LanguageTranslation = {
change_schema: 'Cambiar', change_schema: 'Cambiar',
none: 'nada', none: 'nada',
}, },
// TODO: Translate
export_diagram_dialog: {
title: 'Export Diagram',
description: 'Choose the format for export:',
format_json: 'JSON',
cancel: 'Cancel',
export: 'Export',
error: {
title: 'Error exporting diagram',
description:
'Something went wrong. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_diagram_dialog: {
title: 'Import Diagram',
description: 'Paste the diagram JSON below:',
cancel: 'Cancel',
import: 'Import',
error: {
title: 'Error importing diagram',
description:
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
relationship_type: { relationship_type: {
one_to_one: 'Uno a Uno', one_to_one: 'Uno a Uno',
one_to_many: 'Uno a Muchos', one_to_many: 'Uno a Muchos',
@@ -351,12 +376,25 @@ export const es: LanguageTranslation = {
table_node_context_menu: { table_node_context_menu: {
edit_table: 'Editar Tabla', edit_table: 'Editar Tabla',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Eliminar Tabla', delete_table: 'Eliminar Tabla',
}, },
// TODO: Add translations
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
tool_tips: {
double_click_to_edit: 'Doble clic para editar',
},
language_select: {
change_language: 'Idioma',
},
}, },
}; };
export const esMetadata: LanguageMetadata = { export const esMetadata: LanguageMetadata = {
name: 'Español', name: 'Spanish',
nativeName: 'Español',
code: 'es', code: 'es',
}; };

View File

@@ -15,7 +15,7 @@ export const fr: LanguageTranslation = {
exit: 'Quitter', exit: 'Quitter',
}, },
edit: { edit: {
edit: 'Éditer', edit: 'Édition',
undo: 'Annuler', undo: 'Annuler',
redo: 'Rétablir', redo: 'Rétablir',
clear: 'Effacer', clear: 'Effacer',
@@ -28,10 +28,14 @@ export const fr: LanguageTranslation = {
show_cardinality: 'Afficher la Cardinalité', show_cardinality: 'Afficher la Cardinalité',
zoom_on_scroll: 'Zoom sur le Défilement', zoom_on_scroll: 'Zoom sur le Défilement',
theme: 'Thème', theme: 'Thème',
change_language: 'Langue',
show_dependencies: 'Afficher les Dépendances', show_dependencies: 'Afficher les Dépendances',
hide_dependencies: 'Masquer les Dépendances', hide_dependencies: 'Masquer les Dépendances',
}, },
share: {
share: 'Partage',
export_diagram: 'Exporter le diagramme',
import_diagram: 'Importer un diagramme',
},
help: { help: {
help: 'Aide', help: 'Aide',
visit_website: 'Visitez ChartDB', visit_website: 'Visitez ChartDB',
@@ -130,6 +134,7 @@ export const fr: LanguageTranslation = {
title: 'Actions de la Table', title: 'Actions de la Table',
add_field: 'Ajouter un Champ', add_field: 'Ajouter un Champ',
add_index: 'Ajouter un Index', add_index: 'Ajouter un Index',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Supprimer la Table', delete_table: 'Supprimer la Table',
change_schema: 'Changer le Schéma', change_schema: 'Changer le Schéma',
}, },
@@ -218,6 +223,8 @@ export const fr: LanguageTranslation = {
cancel: 'Annuler', cancel: 'Annuler',
back: 'Retour', back: 'Retour',
// TODO: Translate
import_from_file: 'Import from File',
empty_diagram: 'Diagramme vide', empty_diagram: 'Diagramme vide',
continue: 'Continuer', continue: 'Continuer',
import: 'Importer', import: 'Importer',
@@ -332,7 +339,31 @@ export const fr: LanguageTranslation = {
cancel: 'Annuler', cancel: 'Annuler',
}, },
}, },
// TODO: Translate
export_diagram_dialog: {
title: 'Export Diagram',
description: 'Choose the format for export:',
format_json: 'JSON',
cancel: 'Cancel',
export: 'Export',
error: {
title: 'Error exporting diagram',
description:
'Something went wrong. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_diagram_dialog: {
title: 'Import Diagram',
description: 'Paste the diagram JSON below:',
cancel: 'Cancel',
import: 'Import',
error: {
title: 'Error importing diagram',
description:
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
relationship_type: { relationship_type: {
one_to_one: 'Un à Un', one_to_one: 'Un à Un',
one_to_many: 'Un à Plusieurs', one_to_many: 'Un à Plusieurs',
@@ -347,12 +378,25 @@ export const fr: LanguageTranslation = {
table_node_context_menu: { table_node_context_menu: {
edit_table: 'Éditer la Table', edit_table: 'Éditer la Table',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Supprimer la Table', delete_table: 'Supprimer la Table',
}, },
// TODO: Add translations
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
tool_tips: {
double_click_to_edit: 'Double-cliquez pour modifier',
},
language_select: {
change_language: 'Langue',
},
}, },
}; };
export const frMetadata: LanguageMetadata = { export const frMetadata: LanguageMetadata = {
name: 'Français', name: 'French',
nativeName: 'Français',
code: 'fr', code: 'fr',
}; };

397
src/i18n/locales/gu.ts Normal file
View File

@@ -0,0 +1,397 @@
import type { LanguageMetadata, LanguageTranslation } from '../types';
export const gu: LanguageTranslation = {
translation: {
menu: {
file: {
file: 'ફાઇલ',
new: 'નવું',
open: 'ખોલો',
save: 'સાચવો',
import_database: 'ડેટાબેસ આયાત કરો',
export_sql: 'SQL નિકાસ કરો',
export_as: 'રૂપે નિકાસ કરો',
delete_diagram: 'ડાયાગ્રામ કાઢી નાખો',
exit: 'બહાર જાઓ',
},
edit: {
edit: 'ફેરફાર',
undo: 'અનડુ',
redo: 'રીડુ',
clear: 'સાફ કરો',
},
view: {
view: 'જુઓ',
show_sidebar: 'સાઇડબાર બતાવો',
hide_sidebar: 'સાઇડબાર છુપાવો',
hide_cardinality: 'કાર્ડિનાલિટી છુપાવો',
show_cardinality: 'કાર્ડિનાલિટી બતાવો',
zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો',
theme: 'થિમ',
show_dependencies: 'નિર્ભરતાઓ બતાવો',
hide_dependencies: 'નિર્ભરતાઓ છુપાવો',
},
share: {
share: 'શેર કરો',
export_diagram: 'ડાયાગ્રામ નિકાસ કરો',
import_diagram: 'ડાયાગ્રામ આયાત કરો',
},
help: {
help: 'મદદ',
visit_website: 'ChartDB વેબસાઇટ પર જાઓ',
join_discord: 'અમારા Discordમાં જોડાઓ',
schedule_a_call: 'અમારી સાથે વાત કરો!',
},
},
delete_diagram_alert: {
title: 'ડાયાગ્રામ કાઢી નાખો',
description:
'આ ક્રિયા પરત નહીં લઇ શકાય. આ ડાયાગ્રામ કાયમ માટે કાઢી નાખવામાં આવશે.',
cancel: 'રદ કરો',
delete: 'કાઢી નાખો',
},
clear_diagram_alert: {
title: 'ડાયાગ્રામ સાફ કરો',
description:
'આ ક્રિયા પરત નહીં લઇ શકાય. આ ડાયાગ્રામમાં બધા ડેટા કાયમ માટે કાઢી નાખશે.',
cancel: 'રદ કરો',
clear: 'સાફ કરો',
},
reorder_diagram_alert: {
title: 'ડાયાગ્રામ ફરી વ્યવસ્થિત કરો',
description:
'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?',
reorder: 'ફરી વ્યવસ્થિત કરો',
cancel: 'રદ કરો',
},
multiple_schemas_alert: {
title: 'કઈંક વધારે સ્કીમા',
description:
'{{schemasCount}} સ્કીમા આ ડાયાગ્રામમાં છે. હાલમાં દર્શાવેલ છે: {{formattedSchemas}}.',
dont_show_again: 'ફરીથી ન બતાવો',
change_schema: 'બદલો',
none: 'કઈ નહીં',
},
theme: {
system: 'સિસ્ટમ',
light: 'હલકો',
dark: 'ઘાટો',
},
zoom: {
on: 'ચાલુ',
off: 'બંધ',
},
last_saved: 'છેલ્લે સાચવ્યું',
saved: 'સાચવ્યું',
diagrams: 'ડાયાગ્રામ',
loading_diagram: 'ડાયાગ્રામ લોડ થઈ રહ્યું છે...',
deselect_all: 'બધાને ડીસેલેક્ટ કરો',
select_all: 'બધા પસંદ કરો',
clear: 'સાફ કરો',
show_more: 'વધુ બતાવો',
show_less: 'ઓછું બતાવો',
copy_to_clipboard: 'ક્લિપબોર્ડમાં નકલ કરો',
copied: 'નકલ થયું!',
side_panel: {
schema: 'સ્કીમા:',
filter_by_schema: 'સ્કીમા દ્વારા ફિલ્ટર કરો',
search_schema: 'સ્કીમા શોધો...',
no_schemas_found: 'કોઈ સ્કીમા મળ્યા નથી.',
view_all_options: 'બધા વિકલ્પો જુઓ...',
tables_section: {
tables: 'ટેબલ્સ',
add_table: 'ટેબલ ઉમેરો',
filter: 'ફિલ્ટર',
collapse: 'બધાને સકુચિત કરો',
table: {
fields: 'ફીલ્ડ્સ',
//TODO translate
nullable: 'Nullable?',
primary_key: 'પ્રાથમિક કી',
indexes: 'ઈન્ડેક્સ',
comments: 'ટિપ્પણીઓ',
no_comments: 'કોઈ ટિપ્પણીઓ નથી',
add_field: 'ફીલ્ડ ઉમેરો',
add_index: 'ઈન્ડેક્સ ઉમેરો',
index_select_fields: 'ફીલ્ડ્સ પસંદ કરો',
no_types_found: 'કોઈ પ્રકાર મળ્યા નથી',
field_name: 'નામ',
field_type: 'પ્રકાર',
field_actions: {
title: 'ફીલ્ડ લક્ષણો',
unique: 'અદ્વિતીય',
comments: 'ટિપ્પણીઓ',
no_comments: 'કોઈ ટિપ્પણીઓ નથી',
delete_field: 'ફીલ્ડ કાઢી નાખો',
},
index_actions: {
title: 'ઇન્ડેક્સ લક્ષણો',
name: 'નામ',
unique: 'અદ્વિતીય',
delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
},
table_actions: {
title: 'ટેબલ ક્રિયાઓ',
change_schema: 'સ્કીમા બદલો',
add_field: 'ફીલ્ડ ઉમેરો',
add_index: 'ઇન્ડેક્સ ઉમેરો',
duplicate_table: 'ટેબલ ડુપ્લિકેટ કરો',
delete_table: 'ટેબલ કાઢી નાખો',
},
},
empty_state: {
title: 'કોઈ ટેબલ્સ નથી',
description: 'શરૂ કરવા માટે એક ટેબલ બનાવો',
},
},
relationships_section: {
relationships: 'સંબંધો',
filter: 'ફિલ્ટર',
add_relationship: 'સંબંધ ઉમેરો',
collapse: 'બધાને સકુચિત કરો',
relationship: {
primary: 'પ્રાથમિક ટેબલ',
foreign: 'સંદર્ભ ટેબલ',
cardinality: 'કાર્ડિનાલિટી',
delete_relationship: 'કાઢી નાખો',
relationship_actions: {
title: 'ક્રિયાઓ',
delete_relationship: 'કાઢી નાખો',
},
},
empty_state: {
title: 'કોઈ સંબંધો નથી',
description: 'ટેબલ્સ કનેક્ટ કરવા માટે એક સંબંધ બનાવો',
},
},
dependencies_section: {
dependencies: 'નિર્ભરતાઓ',
filter: 'ફિલ્ટર',
collapse: 'સિકોડો',
dependency: {
table: 'ટેબલ',
dependent_table: 'આધાર રાખેલું ટેબલ',
delete_dependency: 'નિર્ભરતા કાઢી નાખો',
dependency_actions: {
title: 'ક્રિયાઓ',
delete_dependency: 'નિર્ભરતા કાઢી નાખો',
},
},
empty_state: {
title: 'કોઈ નિર્ભરતાઓ નથી',
description: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.',
},
},
},
toolbar: {
zoom_in: 'ઝૂમ ઇન',
zoom_out: 'ઝૂમ આઉટ',
save: 'સાચવો',
show_all: 'બધું બતાવો',
undo: 'અનડુ',
redo: 'રીડુ',
reorder_diagram: 'ડાયાગ્રામ ફરીથી વ્યવસ્થિત કરો',
highlight_overlapping_tables: 'ઓવરલેપ કરતો ટેબલ હાઇલાઇટ કરો',
},
new_diagram_dialog: {
database_selection: {
title: 'તમારું ડેટાબેસ શું છે?',
description: 'દરેક ડેટાબેસની પોતાની ખાસિયતો અને ક્ષમતા હોય છે.',
check_examples_long: 'ઉદાહરણ જુઓ',
check_examples_short: 'ઉદાહરણ',
},
import_database: {
title: 'તમારું ડેટાબેસ આયાત કરો',
database_edition: 'ડેટાબેસ આવૃત્તિ:',
step_1: 'તમારા ડેટાબેસમાં આ સ્ક્રિપ્ટ ચલાવો:',
step_2: 'સ્ક્રિપ્ટનો પરિણામ અહીં પેસ્ટ કરો:',
script_results_placeholder: 'સ્ક્રિપ્ટના પરિણામ અહીં...',
ssms_instructions: {
button_text: 'SSMS સૂચનાઓ',
title: 'સૂચનાઓ',
step_1: 'ટૂલ્સ > વિકલ્પો > ક્વેરી પરિણામો > SQL સર્વર પર જાઓ.',
step_2: 'જો તમે "ગ્રિડમાં પરિણામો" નો ઉપયોગ કરી રહ્યા છો, તો નોન-XML ડેટા માટે મહત્તમ અક્ષરો મેળવવું (9999999 પર સેટ કરો).',
},
instructions_link: 'મદદ જોઈએ? અહીં જુઓ',
check_script_result: 'સ્ક્રિપ્ટ પરિણામ તપાસો',
},
cancel: 'રદ કરો',
back: 'પાછા',
import_from_file: 'ફાઇલમાંથી આયાત કરો',
empty_diagram: 'ખાલી ડાયાગ્રામ',
continue: 'ચાલુ રાખો',
import: 'આયાત કરો',
},
open_diagram_dialog: {
title: 'ડાયાગ્રામ ખોલો',
description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.',
table_columns: {
name: 'નામ',
created_at: 'બનાવાની તારીખ',
last_modified: 'છેલ્લું સુધારેલું',
tables_count: 'ટેબલ્સ',
},
cancel: 'રદ કરો',
open: 'ખોલો',
},
export_sql_dialog: {
title: 'SQL નિકાસ કરો',
description:
'{{databaseType}} સ્ક્રિપ્ટ માટે તમારું ડાયાગ્રામ સ્કીમા નિકાસ કરો',
close: 'બંધ કરો',
loading: {
text: '{{databaseType}} માટે AI SQL બનાવી રહ્યું છે...',
description: 'તેને 30 સેકંડ સુધીનો સમય લાગી શકે છે.',
},
error: {
message:
'SQL સ્ક્રિપ્ટ જનરેટ કરવા દરમિયાન ભૂલ થઈ. કૃપા કરીને પછીથી ફરી પ્રયત્ન કરો અથવા <0>અમારો સંપર્ક કરો</0>.',
description:
'તમારા OPENAI_TOKEN નો ઉપયોગ કરવા માટે મફત અનુભવો, મેન્યુઅલ <0>અહીં જુઓ</0>.',
},
},
create_relationship_dialog: {
title: 'સંબંધ બનાવો',
primary_table: 'પ્રાથમિક ટેબલ',
primary_field: 'પ્રાથમિક ફીલ્ડ',
referenced_table: 'સંદર્ભિત ટેબલ',
referenced_field: 'સંદર્ભિત ફીલ્ડ',
primary_table_placeholder: 'ટેબલ પસંદ કરો',
primary_field_placeholder: 'ફીલ્ડ પસંદ કરો',
referenced_table_placeholder: 'ટેબલ પસંદ કરો',
referenced_field_placeholder: 'ફીલ્ડ પસંદ કરો',
no_tables_found: 'કોઈ ટેબલ મળી નથી',
no_fields_found: 'કોઈ ફીલ્ડ મળી નથી',
create: 'બનાવો',
cancel: 'રદ કરો',
},
import_database_dialog: {
title: 'વર્તમાન ડાયાગ્રામમાં આયાત કરો',
override_alert: {
title: 'ડેટાબેસ આયાત કરો',
content: {
alert: 'આ ડાયાગ્રામ આયાત કરવાથી હાલના ટેબલ્સ અને સંબંધો પર અસર થશે.',
new_tables:
'<bold>{{newTablesNumber}}</bold> નવા ટેબલ ઉમેરવામાં આવશે.',
new_relationships:
'<bold>{{newRelationshipsNumber}}</bold> નવા સંબંધો બનાવવામાં આવશે.',
tables_override:
'<bold>{{tablesOverrideNumber}}</bold> ટેબલ ઓવરરાઇટ કરાશે.',
proceed: 'શું તમે આગળ વધવા માંગો છો?',
},
import: 'આયાત કરો',
cancel: 'રદ કરો',
},
},
export_image_dialog: {
title: 'છબી નિકાસ કરો',
description: 'નિકાસ માટે સ્કેલ ફેક્ટર પસંદ કરો:',
scale_1x: '1x સામાન્ય',
scale_2x: '2x (ભલામણ કરેલું)',
scale_3x: '3x',
scale_4x: '4x',
cancel: 'રદ કરો',
export: 'નિકાસ કરો',
},
new_table_schema_dialog: {
title: 'સ્કીમા પસંદ કરો',
description:
'વર્તમાનમાં ઘણા સ્કીમા દર્શાવવામાં આવે છે. નવું ટેબલ માટે એક પસંદ કરો.',
cancel: 'રદ કરો',
confirm: 'ખાતરી કરો',
},
update_table_schema_dialog: {
title: 'સ્કીમા બદલો',
description: 'ટેબલ "{{tableName}}" માટે સ્કીમા અપડેટ કરો',
cancel: 'રદ કરો',
confirm: 'બદલો',
},
star_us_dialog: {
title: 'અમને સુધારવામાં મદદ કરો!',
description:
'શું તમે GitHub પર અમને સ્ટાર આપી શકો છો? તે માત્ર એક ક્લિક દૂર છે!',
close: 'હાલમાં નહીં',
confirm: 'ખરેખર!',
},
export_diagram_dialog: {
title: 'ડાયાગ્રામ નિકાસ કરો',
description: 'નિકાસ માટે ફોર્મેટ પસંદ કરો:',
format_json: 'JSON',
cancel: 'રદ કરો',
export: 'નિકાસ કરો',
error: {
title: 'ડાયાગ્રામ નિકાસમાં ભૂલ',
description:
'કશુક તો ખોટું થયું. મદદ જોઈએ? chartdb.io@gmail.com પર સંપર્ક કરો.',
},
},
import_diagram_dialog: {
title: 'ડાયાગ્રામ આયાત કરો',
description: 'નીચે ડાયાગ્રામ JSON પેસ્ટ કરો:',
cancel: 'રદ કરો',
import: 'આયાત કરો',
error: {
title: 'ડાયાગ્રામ આયાતમાં ભૂલ',
description:
'ડાયાગ્રામ JSON અમાન્ય છે. કૃપા કરીને JSON તપાસો અને ફરી પ્રયાસ કરો. મદદ જોઈએ? chartdb.io@gmail.com પર સંપર્ક કરો.',
},
},
relationship_type: {
one_to_one: 'એકથી એક',
one_to_many: 'એકથી ઘણા',
many_to_one: 'ઘણા થી એક',
many_to_many: 'ઘણાથી ઘણા',
},
canvas_context_menu: {
new_table: 'નવું ટેબલ',
new_relationship: 'નવો સંબંધ',
},
table_node_context_menu: {
edit_table: 'ટેબલ સંપાદિત કરો',
duplicate_table: 'ટેબલ નકલ કરો',
delete_table: 'ટેબલ કાઢી નાખો',
},
snap_to_grid_tooltip: 'ગ્રિડ પર સ્નેપ કરો (જમાવટ {{key}})',
tool_tips: {
double_click_to_edit: 'સંપાદિત કરવા માટે ડબલ-ક્લિક કરો',
},
language_select: {
change_language: 'ભાષા બદલો',
},
},
};
export const guMetadata: LanguageMetadata = {
name: 'Gujarati',
nativeName: 'ગુજરાતી',
code: 'gu',
};

View File

@@ -28,10 +28,15 @@ export const hi: LanguageTranslation = {
show_cardinality: 'कार्डिनैलिटी दिखाएँ', show_cardinality: 'कार्डिनैलिटी दिखाएँ',
zoom_on_scroll: 'स्क्रॉल पर ज़ूम', zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
theme: 'थीम', theme: 'थीम',
change_language: 'भाषा बदलें',
show_dependencies: 'निर्भरता दिखाएँ', show_dependencies: 'निर्भरता दिखाएँ',
hide_dependencies: 'निर्भरता छिपाएँ', hide_dependencies: 'निर्भरता छिपाएँ',
}, },
// TODO: Translate
share: {
share: 'Share',
export_diagram: 'Export Diagram',
import_diagram: 'Import Diagram',
},
help: { help: {
help: 'मदद', help: 'मदद',
visit_website: 'ChartDB वेबसाइट पर जाएँ', visit_website: 'ChartDB वेबसाइट पर जाएँ',
@@ -140,6 +145,7 @@ export const hi: LanguageTranslation = {
change_schema: 'स्कीमा बदलें', change_schema: 'स्कीमा बदलें',
add_field: 'फ़ील्ड जोड़ें', add_field: 'फ़ील्ड जोड़ें',
add_index: 'सूचकांक जोड़ें', add_index: 'सूचकांक जोड़ें',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'तालिका हटाएँ', delete_table: 'तालिका हटाएँ',
}, },
}, },
@@ -228,6 +234,8 @@ export const hi: LanguageTranslation = {
cancel: 'रद्द करें', cancel: 'रद्द करें',
back: 'वापस', back: 'वापस',
// TODO: Translate
import_from_file: 'Import from File',
empty_diagram: 'खाली आरेख', empty_diagram: 'खाली आरेख',
continue: 'जारी रखें', continue: 'जारी रखें',
import: 'आयात करें', import: 'आयात करें',
@@ -331,7 +339,31 @@ export const hi: LanguageTranslation = {
close: 'अभी नहीं', close: 'अभी नहीं',
confirm: 'बिलकुल!', confirm: 'बिलकुल!',
}, },
// TODO: Translate
export_diagram_dialog: {
title: 'Export Diagram',
description: 'Choose the format for export:',
format_json: 'JSON',
cancel: 'Cancel',
export: 'Export',
error: {
title: 'Error exporting diagram',
description:
'Something went wrong. Need help? chartdb.io@gmail.com',
},
},
// TODO: Translate
import_diagram_dialog: {
title: 'Import Diagram',
description: 'Paste the diagram JSON below:',
cancel: 'Cancel',
import: 'Import',
error: {
title: 'Error importing diagram',
description:
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
},
},
relationship_type: { relationship_type: {
one_to_one: 'एक से एक', one_to_one: 'एक से एक',
one_to_many: 'एक से कई', one_to_many: 'एक से कई',
@@ -346,12 +378,25 @@ export const hi: LanguageTranslation = {
table_node_context_menu: { table_node_context_menu: {
edit_table: 'तालिका संपादित करें', edit_table: 'तालिका संपादित करें',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'तालिका हटाएँ', delete_table: 'तालिका हटाएँ',
}, },
// TODO: Add translations
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
tool_tips: {
double_click_to_edit: 'संपादित करने के लिए डबल-क्लिक करें',
},
language_select: {
change_language: 'भाषा बदलें',
},
}, },
}; };
export const hiMetadata: LanguageMetadata = { export const hiMetadata: LanguageMetadata = {
name: 'Hindi', name: 'Hindi',
nativeName: 'हिन्दी',
code: 'hi', code: 'hi',
}; };

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