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
## [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)

View File

@@ -30,7 +30,7 @@ To get started:
### 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?

View File

@@ -1,5 +1,7 @@
FROM node:22-alpine AS builder
ARG VITE_OPENAI_API_KEY
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
@@ -14,6 +16,7 @@ RUN npm run build
FROM nginx:stable-alpine AS production
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 80

View File

@@ -15,8 +15,9 @@
<h3 align="center">
<a href="https://discord.gg/QeFwyWSKwC">Community</a> &bull;
<a href="https://www.chartdb.io">Website</a> &bull;
<a href="https://app.chartdb.io/examples">Demo</a>
<a href="https://www.chartdb.io?ref=github_readme">Website</a> &bull;
<a href="https://chartdb.io/templates?ref=github_readme">Examples</a> &bull;
<a href="https://app.chartdb.io?ref=github_readme">Demo</a>
</h3>
<h4 align="center">
@@ -38,7 +39,7 @@
---
<p align="center">
<img width='700px' src="./public/ChartDB.png">
<img width='700px' src="./public/chartdb.png">
</p>
### 🎉 ChartDB
@@ -71,7 +72,7 @@ ChartDB is currently in Public Beta. Star and watch this repository to get notif
## 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
@@ -97,7 +98,7 @@ VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
### Running the Docker Container
```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
```
@@ -105,7 +106,7 @@ Open your browser and navigate to `http://localhost:8080`.
## 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"
3. Choose the database that you are using.
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",
"version": "1.0.0",
"version": "1.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "chartdb",
"version": "1.0.0",
"version": "1.3.0",
"dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@dnd-kit/sortable": "^8.0.0",
@@ -44,6 +44,7 @@
"fast-deep-equal": "^3.1.3",
"html-to-image": "^1.11.11",
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.441.0",
"monaco-editor": "^0.52.0",
"nanoid": "^5.0.7",
@@ -60,7 +61,8 @@
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.6",
"vaul": "^0.9.1"
"vaul": "^0.9.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^22.1.0",
@@ -6754,6 +6756,15 @@
"@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": {
"version": "5.3.2",
"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",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View File

@@ -1,95 +1,97 @@
{
"name": "chartdb",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "npm run lint && tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "npm run lint -- --fix",
"preview": "vite preview",
"prepare": "husky"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@dnd-kit/sortable": "^8.0.0",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.3.1",
"ahooks": "^3.8.1",
"ai": "^3.3.14",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"dexie": "^4.0.8",
"fast-deep-equal": "^3.1.3",
"html-to-image": "^1.11.11",
"i18next": "^23.14.0",
"lucide-react": "^0.441.0",
"monaco-editor": "^0.52.0",
"nanoid": "^5.0.7",
"node-sql-parser": "^5.3.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet-async": "^2.0.5",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",
"react-responsive": "^10.0.0",
"react-router-dom": "^6.26.0",
"react-use": "^17.5.1",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.6",
"vaul": "^0.9.1"
},
"devDependencies": {
"@types/node": "^22.1.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"eslint-plugin-tailwindcss": "^3.17.4",
"husky": "^9.1.5",
"postcss": "^8.4.40",
"prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.7",
"typescript": "^5.2.2",
"unplugin-inject-preload": "^3.0.0",
"vite": "^5.3.4"
}
"name": "chartdb",
"private": true,
"version": "1.3.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "npm run lint && tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "npm run lint -- --fix",
"preview": "vite preview",
"prepare": "husky"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@dnd-kit/sortable": "^8.0.0",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.3.1",
"ahooks": "^3.8.1",
"ai": "^3.3.14",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"dexie": "^4.0.8",
"fast-deep-equal": "^3.1.3",
"html-to-image": "^1.11.11",
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.441.0",
"monaco-editor": "^0.52.0",
"nanoid": "^5.0.7",
"node-sql-parser": "^5.3.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet-async": "^2.0.5",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",
"react-responsive": "^10.0.0",
"react-router-dom": "^6.26.0",
"react-use": "^17.5.1",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.6",
"vaul": "^0.9.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^22.1.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"eslint-plugin-tailwindcss": "^3.17.4",
"husky": "^9.1.5",
"postcss": "^8.4.40",
"prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.7",
"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 { DarkTheme } from './themes/dark';
import { LightTheme } from './themes/light';
import './config.ts';
export interface CodeSnippetProps {
className?: string;
code: string;
language?: 'sql' | 'shell';
loading?: boolean;
autoScroll?: boolean;
isComplete?: boolean;
}
export const Editor = lazy(() =>
@@ -24,7 +27,14 @@ export const Editor = lazy(() =>
);
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 monaco = useMonaco();
const { effectiveTheme } = useTheme();
@@ -46,6 +56,16 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
}, 1500);
}, [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(() => {
navigator.clipboard.writeText(code);
setIsCopied(true);
@@ -62,32 +82,38 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
<Spinner />
) : (
<Suspense fallback={<Spinner />}>
<Tooltip
onOpenChange={setTooltipOpen}
open={isCopied || tooltipOpen}
>
<TooltipTrigger
asChild
className="absolute right-1 top-1 z-10"
{isComplete ? (
<Tooltip
onOpenChange={setTooltipOpen}
open={isCopied || tooltipOpen}
>
<span>
<Button
className=" h-fit p-1.5"
variant="outline"
onClick={copyToClipboard}
>
{isCopied ? (
<CopyCheck size={16} />
) : (
<Copy size={16} />
)}
</Button>
</span>
</TooltipTrigger>
<TooltipContent>
{t(isCopied ? 'copied' : 'copy_to_clipboard')}
</TooltipContent>
</Tooltip>
<TooltipTrigger
asChild
className="absolute right-1 top-1 z-10"
>
<span>
<Button
className=" h-fit p-1.5"
variant="outline"
onClick={copyToClipboard}
>
{isCopied ? (
<CopyCheck size={16} />
) : (
<Copy size={16} />
)}
</Button>
</span>
</TooltipTrigger>
<TooltipContent>
{t(
isCopied
? 'copied'
: 'copy_to_clipboard'
)}
</TooltipContent>
</Tooltip>
) : null}
<Editor
value={code}
@@ -117,6 +143,9 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
contextmenu: false,
}}
/>
{!isComplete ? (
<div className="absolute bottom-2 right-2 size-2 animate-blink rounded-full bg-pink-600" />
) : null}
</Suspense>
)}
</div>

View File

@@ -3,6 +3,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons';
import { cn } from '@/lib/utils';
import { ScrollArea } from '../scroll-area/scroll-area';
const Dialog = DialogPrimitive.Root;
@@ -110,6 +111,18 @@ const DialogDescription = React.forwardRef<
));
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 {
Dialog,
DialogPortal,
@@ -121,4 +134,5 @@ export {
DialogFooter,
DialogTitle,
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}
/>
) : null}
{item.title}
<span className="min-w-0 truncate">{item.title}</span>
</Link>
))}
</div>

View File

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

View File

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

View File

@@ -11,8 +11,6 @@ import type { DBRelationship } from '@/lib/domain/db-relationship';
import { useStorage } from '@/hooks/use-storage';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
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 { DBSchema } from '@/lib/domain/db-schema';
import {
@@ -34,13 +32,11 @@ export const ChartDBProvider: React.FC<
> = ({ children, diagram, readonly }) => {
let db = useStorage();
const events = useEventEmitter<ChartDBEvent>();
const navigate = useNavigate();
const { setSchemasFilter, schemasFilter } = useLocalConfig();
const { addUndoAction, resetRedoStack, resetUndoStack } =
useRedoUndoStack();
const [diagramId, setDiagramId] = useState('');
const [diagramName, setDiagramName] = useState('');
const { updateConfig } = useConfig();
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
const [diagramUpdatedAt, setDiagramUpdatedAt] = useState<Date>(new Date());
const [databaseType, setDatabaseType] = useState<DatabaseType>(
@@ -173,34 +169,13 @@ export const ChartDBProvider: React.FC<
resetRedoStack();
resetUndoStack();
const [config] = await Promise.all([
db.getConfig(),
await Promise.all([
db.deleteDiagramTables(diagramId),
db.deleteDiagramRelationships(diagramId),
db.deleteDiagram(diagramId),
db.deleteDiagramDependencies(diagramId),
]);
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,
]);
}, [db, diagramId, resetRedoStack, resetUndoStack]);
const updateDiagramUpdatedAt: ChartDBContext['updateDiagramUpdatedAt'] =
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 { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
export interface DialogContext {
// Create diagram dialog
@@ -48,6 +50,18 @@ export interface DialogContext {
params: Omit<ExportImageDialogProps, 'dialog'>
) => 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>({
@@ -69,4 +83,8 @@ export const dialogContext = createContext<DialogContext>({
closeStarUsDialog: emptyFn,
openExportImageDialog: 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 type { ExportImageDialogProps } 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> = ({
children,
@@ -86,6 +88,14 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
[setOpenTableSchemaDialog]
);
// Export image dialog
const [openExportDiagramDialog, setOpenExportDiagramDialog] =
useState(false);
// Import diagram dialog
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
useState(false);
// Alert dialog
const [showAlert, setShowAlert] = useState(false);
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
@@ -126,6 +136,12 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeStarUsDialog: () => setOpenStarUsDialog(false),
closeExportImageDialog: () => setOpenExportImageDialog(false),
openExportImageDialog: openExportImageDialogHandler,
openExportDiagramDialog: () => setOpenExportDiagramDialog(true),
closeExportDiagramDialog: () =>
setOpenExportDiagramDialog(false),
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
closeImportDiagramDialog: () =>
setOpenImportDiagramDialog(false),
}}
>
{children}
@@ -152,6 +168,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
dialog={{ open: openExportImageDialog }}
{...exportImageDialogParams}
/>
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
</dialogContext.Provider>
);
};

View File

@@ -5,12 +5,14 @@ import { toJpeg, toPng, toSvg } from 'html-to-image';
import { useReactFlow } from '@xyflow/react';
import { useChartDB } from '@/hooks/use-chartdb';
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
import { useTheme } from '@/hooks/use-theme';
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const { hideLoader, showLoader } = useFullScreenLoader();
const { setNodes, getViewport } = useReactFlow();
const { effectiveTheme } = useTheme();
const { diagramName } = useChartDB();
const downloadImage = useCallback(
@@ -59,13 +61,101 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
const imageCreateFn = imageCreatorMap[type];
setTimeout(async () => {
const dataUrl = await imageCreateFn(
window.document.querySelector(
'.react-flow__viewport'
) as HTMLElement,
{
const viewportElement = window.document.querySelector(
'.react-flow__viewport'
) 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'
? { backgroundColor: '#ffffff' }
? {
backgroundColor:
effectiveTheme === 'light'
? '#ffffff'
: '#141414',
}
: {}),
width: reactFlowBounds.width,
height: reactFlowBounds.height,
@@ -76,11 +166,13 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
},
quality: 1,
pixelRatio: scale,
}
);
});
downloadImage(dataUrl, type);
hideLoader();
downloadImage(dataUrl, type);
} finally {
viewportElement.removeChild(tempSvg);
hideLoader();
}
}, 0);
},
[
@@ -90,6 +182,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
imageCreatorMap,
setNodes,
showLoader,
effectiveTheme,
]
);

View File

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

View File

@@ -5,6 +5,7 @@ export enum KeyboardShortcutAction {
UNDO = 'undo',
OPEN_DIAGRAM = 'open_diagram',
SAVE_DIAGRAM = 'save_diagram',
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
}
export interface KeyboardShortcut {
@@ -47,6 +48,13 @@ export const keyboardShortcuts: Record<
keyCombinationMac: 'meta+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 {

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import {
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
@@ -139,6 +140,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
setScriptResult(fixedJson);
setErrorMessage('');
} else {
setScriptResult(fixedJson);
setErrorMessage(errorScriptOutputMessage);
}
@@ -157,188 +159,201 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const renderContent = useCallback(() => {
return (
<div className="flex w-full flex-1 flex-col gap-6">
{databaseTypeToEditionMap[databaseType].length > 0 ? (
<div className="flex flex-col gap-1 md:flex-row">
<p className="text-sm leading-6 text-muted-foreground">
{t(
'new_diagram_dialog.import_database.database_edition'
)}
</p>
<ToggleGroup
type="single"
className="ml-1 flex-wrap gap-2"
value={
!databaseEdition ? 'regular' : databaseEdition
}
onValueChange={(value) => {
setDatabaseEdition(
value === 'regular'
? undefined
: (value as DatabaseEdition)
);
}}
>
<ToggleGroupItem
value="regular"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
<DialogInternalContent>
<div className="flex w-full flex-1 flex-col gap-6">
{databaseTypeToEditionMap[databaseType].length > 0 ? (
<div className="flex flex-col gap-1 md:flex-row">
<p className="text-sm leading-6 text-muted-foreground">
{t(
'new_diagram_dialog.import_database.database_edition'
)}
</p>
<ToggleGroup
type="single"
className="ml-1 flex-wrap gap-2"
value={
!databaseEdition
? 'regular'
: databaseEdition
}
onValueChange={(value) => {
setDatabaseEdition(
value === 'regular'
? undefined
: (value as DatabaseEdition)
);
}}
>
<Avatar className="size-4 rounded-none">
<AvatarImage
src={
databaseSecondaryLogoMap[
databaseType
]
}
alt="Regular"
/>
<AvatarFallback>Regular</AvatarFallback>
</Avatar>
Regular
</ToggleGroupItem>
{databaseTypeToEditionMap[databaseType].map(
(edition) => (
<ToggleGroupItem
value={edition}
key={edition}
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
>
<Avatar className="size-4">
<AvatarImage
src={
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>
{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"
<ToggleGroupItem
value="regular"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
>
<Avatar className="size-4 rounded-none">
<AvatarImage
src={
databaseSecondaryLogoMap[
databaseType
]
}
alt="Regular"
/>
<AvatarFallback>Regular</AvatarFallback>
</Avatar>
Regular
</ToggleGroupItem>
{databaseTypeToEditionMap[databaseType].map(
(edition) => (
<ToggleGroupItem
value={edition}
key={edition}
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
>
{databaseClientToLabelMap[client]}
</TabsTrigger>
)) ?? []}
</TabsList>
<Avatar className="size-4">
<AvatarImage
src={
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>
{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
className="h-40 w-full"
className="h-40 w-full flex-auto"
loading={!importMetadataScripts}
code={
importMetadataScripts?.[databaseType]?.({
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}
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>
<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'
)}
</div>
) : null}
value={scriptResult}
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>
</DialogInternalContent>
);
}, [
databaseEdition,

View File

@@ -128,7 +128,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
}}
>
<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}
>
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (

View File

@@ -5,11 +5,13 @@ import {
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { DatabaseType } from '@/lib/domain/database-type';
import { useTranslation } from 'react-i18next';
import { SelectDatabaseContent } from './select-database-content';
import { useDialog } from '@/hooks/use-dialog';
export interface SelectDatabaseProps {
onContinue: () => void;
@@ -27,6 +29,7 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
createNewDiagram,
}) => {
const { t } = useTranslation();
const { openImportDiagramDialog } = useDialog();
return (
<>
@@ -38,11 +41,13 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
{t('new_diagram_dialog.database_selection.description')}
</DialogDescription>
</DialogHeader>
<SelectDatabaseContent
databaseType={databaseType}
onContinue={onContinue}
setDatabaseType={setDatabaseType}
/>
<DialogInternalContent>
<SelectDatabaseContent
databaseType={databaseType}
onContinue={onContinue}
setDatabaseType={setDatabaseType}
/>
</DialogInternalContent>
<DialogFooter className="mt-4 flex !justify-between gap-2">
{hasExistingDiagram ? (
<DialogClose asChild>
@@ -51,7 +56,13 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
</Button>
</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">
<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,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { Label } from '@/components/label/label';
@@ -20,7 +21,7 @@ import {
import { databaseTypeToLabelMap } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type';
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 type { BaseDialogProps } from '../common/base-dialog-props';
@@ -37,28 +38,47 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const { t } = useTranslation();
const [script, setScript] = React.useState<string>();
const [error, setError] = React.useState<boolean>(false);
const [isScriptLoading, setIsScriptLoading] =
React.useState<boolean>(false);
const abortControllerRef = useRef<AbortController | null>(null);
const exportSQLScript = useCallback(async () => {
if (targetDatabaseType === DatabaseType.GENERIC) {
return Promise.resolve(exportBaseSQL(currentDiagram));
} 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]);
useEffect(() => {
if (!dialog.open) return;
if (!dialog.open) {
abortControllerRef.current?.abort();
return;
}
abortControllerRef.current = new AbortController();
setScript(undefined);
setError(false);
const fetchScript = async () => {
try {
setIsScriptLoading(true);
const script = await exportSQLScript();
setScript(script);
setIsScriptLoading(false);
} catch (e) {
setError(true);
}
};
fetchScript();
return () => {
abortControllerRef.current?.abort();
};
}, [dialog.open, setScript, exportSQLScript, setError]);
const renderError = useCallback(
@@ -132,7 +152,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
}}
>
<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
>
<DialogHeader>
@@ -148,18 +168,24 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
})}
</DialogDescription>
</DialogHeader>
<div className="flex flex-1 items-center justify-center">
{error ? (
renderError()
) : script === undefined ? (
renderLoader()
) : script.length === 0 ? (
renderError()
) : (
<CodeSnippet className="h-96 w-full" code={script!} />
)}
</div>
<DialogInternalContent>
<div className="flex flex-1 items-center justify-center">
{error ? (
renderError()
) : script === undefined ? (
renderLoader()
) : script.length === 0 ? (
renderError()
) : (
<CodeSnippet
className="h-96 w-full"
code={script!}
autoScroll={true}
isComplete={!isScriptLoading}
/>
)}
</div>
</DialogInternalContent>
<DialogFooter className="flex !justify-between gap-2">
<div />
<DialogClose asChild>

View File

@@ -323,7 +323,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
}}
>
<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
>
<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,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { ScrollArea } from '@/components/scroll-area/scroll-area';
import {
Table,
TableBody,
@@ -74,7 +74,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
}}
>
<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
>
<DialogHeader>
@@ -83,9 +83,9 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
{t('open_diagram_dialog.description')}
</DialogDescription>
</DialogHeader>
<div className="flex flex-1 items-center justify-center">
<ScrollArea className="h-80 w-full">
<Table className="h-fit">
<DialogInternalContent>
<div className="flex flex-1 items-center justify-center">
<Table>
<TableHeader className="sticky top-0 bg-background">
<TableRow>
<TableHead />
@@ -155,8 +155,8 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
))}
</TableBody>
</Table>
</ScrollArea>
</div>
</div>
</DialogInternalContent>
<DialogFooter className="flex !justify-between gap-2">
<DialogClose asChild>

View File

@@ -3,69 +3,73 @@
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--subtitle: 215.3 19.3% 34.5%;
}
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--subtitle: 215.3 19.3% 34.5%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--subtitle: 212.7 26.8% 83.9%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--subtitle: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
* {
@apply border-border;
}
body {
@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
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 name="twitter:card" content="summary_large_image" />
@@ -32,7 +32,7 @@ export const HelmetData: React.FC = () => (
/>
<meta
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>
</Helmet>

View File

@@ -1,12 +1,48 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import type { LanguageMetadata } from './types';
import { en, enMetadata } from './locales/en';
import { es } from './locales/es';
import { fr } from './locales/fr';
import { de } from './locales/de';
import { hi } from './locales/hi';
import { ja } from './locales/ja';
import { pt_BR } from './locales/pt_BR';
import { es, esMetadata } from './locales/es';
import { fr, frMetadata } from './locales/fr';
import { de, deMetadata } from './locales/de';
import { hi, hiMetadata } from './locales/hi';
import { ja, jaMetadata } from './locales/ja';
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 = {
en,
@@ -15,17 +51,30 @@ const resources = {
de,
hi,
ja,
ko_KR,
pt_BR,
uk,
ru,
zh_CN,
zh_TW,
ne,
mr,
tr,
id_ID,
te,
gu,
vi,
};
i18n.use(initReactI18next).init({
resources,
lng: enMetadata.code,
interpolation: {
escapeValue: false,
},
fallbackLng: enMetadata.code,
debug: false,
});
i18n.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
interpolation: {
escapeValue: false,
},
fallbackLng: enMetadata.code,
debug: false,
});
export { i18n };

View File

@@ -28,10 +28,15 @@ export const de: LanguageTranslation = {
show_cardinality: 'Kardinalität anzeigen',
zoom_on_scroll: 'Zoom beim Scrollen',
theme: 'Stil',
change_language: 'Sprache',
show_dependencies: 'Abhängigkeiten anzeigen',
hide_dependencies: 'Abhängigkeiten ausblenden',
},
// TODO: Translate
share: {
share: 'Share',
export_diagram: 'Export Diagram',
import_diagram: 'Import Diagram',
},
help: {
help: 'Hilfe',
visit_website: 'ChartDB Webseite',
@@ -139,6 +144,7 @@ export const de: LanguageTranslation = {
change_schema: 'Schema ändern',
add_field: 'Feld hinzufügen',
add_index: 'Index hinzufügen',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Tabelle löschen',
},
},
@@ -226,6 +232,8 @@ export const de: LanguageTranslation = {
cancel: 'Abbrechen',
back: 'Zurück',
// TODO: Translate
import_from_file: 'Import from File',
empty_diagram: 'Leeres Diagramm',
continue: 'Weiter',
import: 'Importieren',
@@ -329,7 +337,31 @@ export const de: LanguageTranslation = {
close: 'Nicht jetzt',
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: {
one_to_one: 'Ein zu Eins (1:1)',
one_to_many: 'Ein zu Viele (1:n)',
@@ -344,12 +376,25 @@ export const de: LanguageTranslation = {
table_node_context_menu: {
edit_table: 'Tabelle bearbeiten',
duplicate_table: 'Duplicate Table', // TODO: Translate
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 = {
name: 'Deutsch',
name: 'German',
nativeName: 'Deutsch',
code: 'de',
};

View File

@@ -28,10 +28,14 @@ export const en = {
show_cardinality: 'Show Cardinality',
zoom_on_scroll: 'Zoom on Scroll',
theme: 'Theme',
change_language: 'Language',
show_dependencies: 'Show Dependencies',
hide_dependencies: 'Hide Dependencies',
},
share: {
share: 'Share',
export_diagram: 'Export Diagram',
import_diagram: 'Import Diagram',
},
help: {
help: 'Help',
visit_website: 'Visit ChartDB',
@@ -139,6 +143,7 @@ export const en = {
change_schema: 'Change Schema',
add_field: 'Add Field',
add_index: 'Add Index',
duplicate_table: 'Duplicate Table',
delete_table: 'Delete Table',
},
},
@@ -224,6 +229,7 @@ export const en = {
},
cancel: 'Cancel',
import_from_file: 'Import from File',
back: 'Back',
empty_diagram: 'Empty diagram',
continue: 'Continue',
@@ -328,7 +334,30 @@ export const en = {
close: 'Not now',
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: {
one_to_one: 'One to One',
one_to_many: 'One to Many',
@@ -343,12 +372,24 @@ export const en = {
table_node_context_menu: {
edit_table: 'Edit Table',
duplicate_table: 'Duplicate 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 = {
name: 'English',
nativeName: 'English',
code: 'en',
};

View File

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

View File

@@ -15,7 +15,7 @@ export const fr: LanguageTranslation = {
exit: 'Quitter',
},
edit: {
edit: 'Éditer',
edit: 'Édition',
undo: 'Annuler',
redo: 'Rétablir',
clear: 'Effacer',
@@ -28,10 +28,14 @@ export const fr: LanguageTranslation = {
show_cardinality: 'Afficher la Cardinalité',
zoom_on_scroll: 'Zoom sur le Défilement',
theme: 'Thème',
change_language: 'Langue',
show_dependencies: 'Afficher les Dépendances',
hide_dependencies: 'Masquer les Dépendances',
},
share: {
share: 'Partage',
export_diagram: 'Exporter le diagramme',
import_diagram: 'Importer un diagramme',
},
help: {
help: 'Aide',
visit_website: 'Visitez ChartDB',
@@ -130,6 +134,7 @@ export const fr: LanguageTranslation = {
title: 'Actions de la Table',
add_field: 'Ajouter un Champ',
add_index: 'Ajouter un Index',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Supprimer la Table',
change_schema: 'Changer le Schéma',
},
@@ -218,6 +223,8 @@ export const fr: LanguageTranslation = {
cancel: 'Annuler',
back: 'Retour',
// TODO: Translate
import_from_file: 'Import from File',
empty_diagram: 'Diagramme vide',
continue: 'Continuer',
import: 'Importer',
@@ -332,7 +339,31 @@ export const fr: LanguageTranslation = {
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: {
one_to_one: 'Un à Un',
one_to_many: 'Un à Plusieurs',
@@ -347,12 +378,25 @@ export const fr: LanguageTranslation = {
table_node_context_menu: {
edit_table: 'Éditer la Table',
duplicate_table: 'Duplicate Table', // TODO: Translate
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 = {
name: 'Français',
name: 'French',
nativeName: 'Français',
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: 'कार्डिनैलिटी दिखाएँ',
zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
theme: 'थीम',
change_language: 'भाषा बदलें',
show_dependencies: 'निर्भरता दिखाएँ',
hide_dependencies: 'निर्भरता छिपाएँ',
},
// TODO: Translate
share: {
share: 'Share',
export_diagram: 'Export Diagram',
import_diagram: 'Import Diagram',
},
help: {
help: 'मदद',
visit_website: 'ChartDB वेबसाइट पर जाएँ',
@@ -140,6 +145,7 @@ export const hi: LanguageTranslation = {
change_schema: 'स्कीमा बदलें',
add_field: 'फ़ील्ड जोड़ें',
add_index: 'सूचकांक जोड़ें',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'तालिका हटाएँ',
},
},
@@ -228,6 +234,8 @@ export const hi: LanguageTranslation = {
cancel: 'रद्द करें',
back: 'वापस',
// TODO: Translate
import_from_file: 'Import from File',
empty_diagram: 'खाली आरेख',
continue: 'जारी रखें',
import: 'आयात करें',
@@ -331,7 +339,31 @@ export const hi: LanguageTranslation = {
close: 'अभी नहीं',
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: {
one_to_one: 'एक से एक',
one_to_many: 'एक से कई',
@@ -346,12 +378,25 @@ export const hi: LanguageTranslation = {
table_node_context_menu: {
edit_table: 'तालिका संपादित करें',
duplicate_table: 'Duplicate Table', // TODO: Translate
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 = {
name: 'Hindi',
nativeName: 'हिन्दी',
code: 'hi',
};

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