Compare commits

..

56 Commits

Author SHA1 Message Date
Guy Ben-Aharon
29b8edc051 chore(main): release 1.4.0 (#453) 2024-12-02 13:30:27 +02:00
Huy Bui
5fc10a7e64 fix(clipboard): defensive for navigator clipboard (#462)
* fix: defensive for navigator clipboard

* add dot in en.ts

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-12-02 13:27:13 +02:00
Guy Ben-Aharon
807cd22e0c feat(clickhouse): add ClickHouse support (#463) 2024-12-01 13:31:24 +02:00
Jonathan Fishner
03772f6b4f feat(add templates): add six more templates (django-axes, laravel-activitylog, octobox, pay-rails, pixelfed, polr) (#460) 2024-11-30 20:51:35 +02:00
Niharika Goulikar
885eb719de feat(i18n): Added bangla translations (#432) 2024-11-30 09:51:47 +02:00
Elliott Zwertvaegher
94656ec7a5 fix(mariadb-types): Add uuid data type (#459)
Madiadb is missing the UUID data type. I simply added it in `src/lib/data/data-types/mariadb-data-types.ts`
2024-11-29 12:21:43 +02:00
Zer0S2m
a0e966b64f feat(side-panel): Add functionality of order tables by drag & drop (#425) 2024-11-28 19:07:51 +02:00
Guy Ben-Aharon
a8fe491c1b fix(import-database): update database type after importing into an existing generic diagra (#456)
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2024-11-27 16:59:07 +02:00
Jonathan Fishner
ddeef3b134 feat(add templates): add six more templates (reversion, screeenly, staytus, deployer, devise, talk) (#457) 2024-11-27 16:18:51 +02:00
ntoniazzi
d45677e92d fix(Last Saved): Translate the "last saved" relative date message (#400)
* Translate the "last saved" relative date message

* dynamic import locale base on the selected lang

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-27 15:07:18 +02:00
Guy Ben-Aharon
9c7d03c285 fix: window type (#454) 2024-11-26 18:17:59 +02:00
Jonathan Fishner
be1b109f23 feat(add templates): add six more templates (#452)
* feat(add templates): add six more templates (taggit, orchid, flipper, doorkeeper, canvas, cachet)

* fix build
2024-11-26 18:07:41 +02:00
Guy Ben-Aharon
05eaf85a3d update readme (#450) 2024-11-26 14:22:50 +02:00
Guy Ben-Aharon
53f443d452 chore(main): release 1.3.1 (#449) 2024-11-26 12:39:41 +02:00
paulhansford
134c62f931 Update Docker Instructions in README.md (#446)
* Update README.md

Add section for running Docker container locally direct from GitHub's Container Registry image as published by project's GitHub Actions workflow

* update readme to have OPENAI_API_KEY as an env var

* capital

* space

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-26 12:36:55 +02:00
Guy Ben-Aharon
4bb4766e1a fix(docker): make OPENAI_API_KEY optional in docker run (#448) 2024-11-26 12:32:20 +02:00
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
206 changed files with 100163 additions and 641 deletions

View File

@@ -1,5 +1,84 @@
# Changelog # Changelog
## [1.4.0](https://github.com/chartdb/chartdb/compare/v1.3.1...v1.4.0) (2024-12-02)
### Features
* **add templates:** add six more templates ([#452](https://github.com/chartdb/chartdb/issues/452)) ([be1b109](https://github.com/chartdb/chartdb/commit/be1b109f23e62df4cc63fa8914c2754f7809cc08))
* **add templates:** add six more templates (django-axes, laravel-activitylog, octobox, pay-rails, pixelfed, polr) ([#460](https://github.com/chartdb/chartdb/issues/460)) ([03772f6](https://github.com/chartdb/chartdb/commit/03772f6b4f99f9c4350356aa0f2a4666f4f1794d))
* **add templates:** add six more templates (reversion, screeenly, staytus, deployer, devise, talk) ([#457](https://github.com/chartdb/chartdb/issues/457)) ([ddeef3b](https://github.com/chartdb/chartdb/commit/ddeef3b134efa893e1c1e15e2f87c27157200e2d))
* **clickhouse:** add ClickHouse support ([#463](https://github.com/chartdb/chartdb/issues/463)) ([807cd22](https://github.com/chartdb/chartdb/commit/807cd22e0c739f339fa07fe1d2f043c5411ae41f))
* **i18n:** Added bangla translations ([#432](https://github.com/chartdb/chartdb/issues/432)) ([885eb71](https://github.com/chartdb/chartdb/commit/885eb719de577c2652fbed1ed287f38fcc98c148))
* **side-panel:** Add functionality of order tables by drag & drop ([#425](https://github.com/chartdb/chartdb/issues/425)) ([a0e966b](https://github.com/chartdb/chartdb/commit/a0e966b64f8070d4595d47b2fb39e8bbf427b794))
### Bug Fixes
* **clipboard:** defensive for navigator clipboard ([#462](https://github.com/chartdb/chartdb/issues/462)) ([5fc10a7](https://github.com/chartdb/chartdb/commit/5fc10a7e649fc5877bb297b519b1b6a8b81f1323))
* **import-database:** update database type after importing into an existing generic diagra ([#456](https://github.com/chartdb/chartdb/issues/456)) ([a8fe491](https://github.com/chartdb/chartdb/commit/a8fe491c1b5a30d9f4144cefa9111dd3dfd5df1a))
* **Last Saved:** Translate the "last saved" relative date message ([#400](https://github.com/chartdb/chartdb/issues/400)) ([d45677e](https://github.com/chartdb/chartdb/commit/d45677e92d72efc6cea8f865ce46f0be6ec9961f))
* **mariadb-types:** Add uuid data type ([#459](https://github.com/chartdb/chartdb/issues/459)) ([94656ec](https://github.com/chartdb/chartdb/commit/94656ec7a5435c2da262fb3bc6a6d381d554b0c1))
* window type ([#454](https://github.com/chartdb/chartdb/issues/454)) ([9c7d03c](https://github.com/chartdb/chartdb/commit/9c7d03c285ff6f818eef3199c9b7a530d03a1fec))
## [1.3.1](https://github.com/chartdb/chartdb/compare/v1.3.0...v1.3.1) (2024-11-26)
### Bug Fixes
* **docker:** make OPENAI_API_KEY optional in docker run ([#448](https://github.com/chartdb/chartdb/issues/448)) ([4bb4766](https://github.com/chartdb/chartdb/commit/4bb4766e1ac8d69e138668eb8a46de5affe62ceb))
## [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) ## [1.1.0](https://github.com/chartdb/chartdb/compare/v1.0.1...v1.1.0) (2024-11-13)

View File

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

View File

@@ -16,8 +16,11 @@ RUN npm run build
FROM nginx:stable-alpine AS production FROM nginx:stable-alpine AS production
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
COPY ./default.conf.template /etc/nginx/conf.d/default.conf.template
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Expose the default port for the Nginx web server # Expose the default port for the Nginx web server
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"] ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -16,6 +16,7 @@
<h3 align="center"> <h3 align="center">
<a href="https://discord.gg/QeFwyWSKwC">Community</a> &bull; <a href="https://discord.gg/QeFwyWSKwC">Community</a> &bull;
<a href="https://www.chartdb.io?ref=github_readme">Website</a> &bull; <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> <a href="https://app.chartdb.io?ref=github_readme">Demo</a>
</h3> </h3>
@@ -94,11 +95,15 @@ npm install
VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
``` ```
### Running the Docker Container ### Run the Docker Container
```bash ```bash
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 -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 ghcr.io/chartdb/chartdb:latest
docker run -p 8080:80 chartdb ```
#### Build and Run locally
```bash
docker build -t chartdb .
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 chartdb
``` ```
Open your browser and navigate to `http://localhost:8080`. Open your browser and navigate to `http://localhost:8080`.

20
default.conf.template Normal file
View File

@@ -0,0 +1,20 @@
server {
listen 80;
listen [::]:80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /config.js {
default_type application/javascript;
return 200 "window.env = { OPENAI_API_KEY: \"$OPENAI_API_KEY\" };";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

7
entrypoint.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Replace placeholders in nginx.conf
envsubst '${OPENAI_API_KEY}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
# Start Nginx
nginx -g "daemon off;"

View File

@@ -12,6 +12,7 @@
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap" href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<script src="/config.js"></script>
<script <script
src="https://cdn.usefathom.com/script.js" src="https://cdn.usefathom.com/script.js"
data-site="PRHIVBNN" data-site="PRHIVBNN"

14
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "chartdb", "name": "chartdb",
"version": "1.1.0", "version": "1.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "chartdb", "name": "chartdb",
"version": "1.1.0", "version": "1.4.0",
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^0.0.51", "@ai-sdk/openai": "^0.0.51",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
@@ -44,6 +44,7 @@
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"i18next": "^23.14.0", "i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.441.0", "lucide-react": "^0.441.0",
"monaco-editor": "^0.52.0", "monaco-editor": "^0.52.0",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
@@ -6755,6 +6756,15 @@
"@babel/runtime": "^7.23.2" "@babel/runtime": "^7.23.2"
} }
}, },
"node_modules/i18next-browser-languagedetector": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",

View File

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

0
public/config.js Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 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: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 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: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 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: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 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: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 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: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 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: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 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: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 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: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 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: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

View File

@@ -3,6 +3,7 @@ import React, { lazy, Suspense, useCallback, useEffect } from 'react';
import { Spinner } from '../spinner/spinner'; import { Spinner } from '../spinner/spinner';
import { useTheme } from '@/hooks/use-theme'; import { useTheme } from '@/hooks/use-theme';
import { useMonaco } from '@monaco-editor/react'; import { useMonaco } from '@monaco-editor/react';
import { useToast } from '@/components/toast/use-toast';
import { Button } from '../button/button'; import { Button } from '../button/button';
import { Copy, CopyCheck } from 'lucide-react'; import { Copy, CopyCheck } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
@@ -38,6 +39,7 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
const { t } = useTranslation(); const { t } = useTranslation();
const monaco = useMonaco(); const monaco = useMonaco();
const { effectiveTheme } = useTheme(); const { effectiveTheme } = useTheme();
const { toast } = useToast();
const [isCopied, setIsCopied] = React.useState(false); const [isCopied, setIsCopied] = React.useState(false);
const [tooltipOpen, setTooltipOpen] = React.useState(false); const [tooltipOpen, setTooltipOpen] = React.useState(false);
@@ -66,10 +68,32 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
} }
}, [code, monaco, autoScroll]); }, [code, monaco, autoScroll]);
const copyToClipboard = useCallback(() => { const copyToClipboard = useCallback(async () => {
navigator.clipboard.writeText(code); if (!navigator?.clipboard) {
setIsCopied(true); toast({
}, [code]); title: t('copy_to_clipboard_toast.unsupported.title'),
variant: 'destructive',
description: t(
'copy_to_clipboard_toast.unsupported.description'
),
});
return;
}
try {
await navigator.clipboard.writeText(code);
setIsCopied(true);
} catch (error) {
setIsCopied(false);
toast({
title: t('copy_to_clipboard_toast.failed.title'),
variant: 'destructive',
description: t(
'copy_to_clipboard_toast.failed.description'
),
});
}
}, [code, t, toast]);
return ( return (
<div <div

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ export interface SelectBoxOption {
description?: string; description?: string;
} }
interface SelectBoxProps { export interface SelectBoxProps {
options: SelectBoxOption[]; options: SelectBoxOption[];
value?: string[] | string; value?: string[] | string;
onChange?: (values: string[] | string) => void; onChange?: (values: string[] | string) => void;

View File

@@ -27,6 +27,7 @@ export interface ChartDBProviderProps {
diagram?: Diagram; diagram?: Diagram;
readonly?: boolean; readonly?: boolean;
} }
export const ChartDBProvider: React.FC< export const ChartDBProvider: React.FC<
React.PropsWithChildren<ChartDBProviderProps> React.PropsWithChildren<ChartDBProviderProps>
> = ({ children, diagram, readonly }) => { > = ({ children, diagram, readonly }) => {
@@ -310,6 +311,7 @@ export const ChartDBProvider: React.FC<
color: randomColor(), color: randomColor(),
createdAt: Date.now(), createdAt: Date.now(),
isView: false, isView: false,
order: tables.length,
...attributes, ...attributes,
}; };
await addTable(table); await addTable(table);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -122,6 +122,18 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
config: '++id, defaultDiagramId', config: '++id, defaultDiagramId',
}); });
db.version(8).stores({
diagrams:
'++id, name, databaseType, databaseEdition, createdAt, updatedAt',
db_tables:
'++id, diagramId, name, schema, x, y, fields, indexes, color, createdAt, width, comment, isView, isMaterializedView, order',
db_relationships:
'++id, diagramId, name, sourceSchema, sourceTableId, targetSchema, targetTableId, sourceFieldId, targetFieldId, type, createdAt',
db_dependencies:
'++id, diagramId, schema, tableId, dependentSchema, dependentTableId, createdAt',
config: '++id, defaultDiagramId',
});
db.on('ready', async () => { db.on('ready', async () => {
const config = await getConfig(); const config = await getConfig();
@@ -345,15 +357,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
.equals(diagramId) .equals(diagramId)
.toArray(); .toArray();
// Sort tables first alphabetically, then views alphabetically return tables;
return tables.sort((a, b) => {
if (a.isView === b.isView) {
// Both are either tables or views, so sort alphabetically by name
return a.name.localeCompare(b.name);
}
// If one is a view and the other is not, put tables first
return a.isView ? 1 : -1;
});
}; };
const addRelationship: StorageContext['addRelationship'] = async ({ const addRelationship: StorageContext['addRelationship'] = async ({

View File

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

View File

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

View File

@@ -8,10 +8,13 @@ export interface ExampleOptionProps {}
export const ExampleOption: React.FC<ExampleOptionProps> = () => { export const ExampleOption: React.FC<ExampleOptionProps> = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Link href="/examples" className="text-primary hover:text-primary"> <Link
<div className="flex size-20 cursor-pointer flex-col items-center rounded-md border py-3 text-center md:size-32"> href="/examples"
<div className="flex flex-1 items-center"> className="col-span-3 text-primary hover:text-primary"
<LayoutGrid size={34} /> >
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 rounded-md border py-3 text-center md:h-12">
<div className="flex items-center">
<LayoutGrid className="size-5 md:size-6" />
</div> </div>
<div className="flex flex-col-reverse"> <div className="flex flex-col-reverse">
<div className="hidden text-sm text-primary md:flex"> <div className="hidden text-sm text-primary md:flex">

View File

@@ -35,6 +35,7 @@ export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
<DatabaseOption type={DatabaseType.MARIADB} /> <DatabaseOption type={DatabaseType.MARIADB} />
<DatabaseOption type={DatabaseType.SQLITE} /> <DatabaseOption type={DatabaseType.SQLITE} />
<DatabaseOption type={DatabaseType.SQL_SERVER} /> <DatabaseOption type={DatabaseType.SQL_SERVER} />
<DatabaseOption type={DatabaseType.CLICKHOUSE} />
<ExampleOption /> <ExampleOption />
</ToggleGroup> </ToggleGroup>
</div> </div>

View File

@@ -5,6 +5,7 @@ import {
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogInternalContent,
DialogTitle, DialogTitle,
} from '@/components/dialog/dialog'; } from '@/components/dialog/dialog';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
@@ -40,11 +41,13 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
{t('new_diagram_dialog.database_selection.description')} {t('new_diagram_dialog.database_selection.description')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<SelectDatabaseContent <DialogInternalContent>
databaseType={databaseType} <SelectDatabaseContent
onContinue={onContinue} databaseType={databaseType}
setDatabaseType={setDatabaseType} onContinue={onContinue}
/> setDatabaseType={setDatabaseType}
/>
</DialogInternalContent>
<DialogFooter className="mt-4 flex !justify-between gap-2"> <DialogFooter className="mt-4 flex !justify-between gap-2">
{hasExistingDiagram ? ( {hasExistingDiagram ? (
<DialogClose asChild> <DialogClose asChild>

View File

@@ -18,6 +18,8 @@ import { useChartDB } from '@/hooks/use-chartdb';
import { diagramToJSONOutput } from '@/lib/export-import-utils'; import { diagramToJSONOutput } from '@/lib/export-import-utils';
import { Spinner } from '@/components/spinner/spinner'; import { Spinner } from '@/components/spinner/spinner';
import { waitFor } from '@/lib/utils'; import { waitFor } from '@/lib/utils';
import { AlertCircle } from 'lucide-react';
import { Alert, AlertDescription, AlertTitle } from '@/components/alert/alert';
export interface ExportDiagramDialogProps extends BaseDialogProps {} export interface ExportDiagramDialogProps extends BaseDialogProps {}
@@ -28,10 +30,12 @@ export const ExportDiagramDialog: React.FC<ExportDiagramDialogProps> = ({
const { diagramName, currentDiagram } = useChartDB(); const { diagramName, currentDiagram } = useChartDB();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { closeExportDiagramDialog } = useDialog(); const { closeExportDiagramDialog } = useDialog();
const [error, setError] = useState(false);
useEffect(() => { useEffect(() => {
if (!dialog.open) return; if (!dialog.open) return;
setIsLoading(false); setIsLoading(false);
setError(false);
}, [dialog.open]); }, [dialog.open]);
const downloadOutput = useCallback( const downloadOutput = useCallback(
@@ -47,12 +51,19 @@ export const ExportDiagramDialog: React.FC<ExportDiagramDialogProps> = ({
const handleExport = useCallback(async () => { const handleExport = useCallback(async () => {
setIsLoading(true); setIsLoading(true);
await waitFor(1000); await waitFor(1000);
const json = diagramToJSONOutput(currentDiagram); try {
const blob = new Blob([json], { type: 'application/json' }); const json = diagramToJSONOutput(currentDiagram);
const dataUrl = URL.createObjectURL(blob); const blob = new Blob([json], { type: 'application/json' });
downloadOutput(dataUrl); const dataUrl = URL.createObjectURL(blob);
setIsLoading(false); downloadOutput(dataUrl);
closeExportDiagramDialog(); setIsLoading(false);
closeExportDiagramDialog();
} catch (e) {
setError(true);
setIsLoading(false);
throw e;
}
}, [downloadOutput, currentDiagram, closeExportDiagramDialog]); }, [downloadOutput, currentDiagram, closeExportDiagramDialog]);
const outputTypeOptions: SelectBoxOption[] = useMemo( const outputTypeOptions: SelectBoxOption[] = useMemo(
@@ -90,6 +101,17 @@ export const ExportDiagramDialog: React.FC<ExportDiagramDialogProps> = ({
value="json" value="json"
/> />
</div> </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> </div>
<DialogFooter className="flex gap-1 md:justify-between"> <DialogFooter className="flex gap-1 md:justify-between">
<DialogClose asChild> <DialogClose asChild>

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