Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
24db32369a | ||
|
e77ee60a5b | ||
|
6c65c2e9cc | ||
|
70f545f78b | ||
|
fb702c87ce | ||
|
eaa067814f | ||
|
667685ed0f | ||
|
2940431efa | ||
|
94ec43b608 | ||
|
a2efed803f | ||
|
8749591be0 | ||
|
c5e0ea6fa4 | ||
|
ab07da0b03 | ||
|
8397bef392 | ||
|
7c3c62860e | ||
|
76ba4ce4c5 | ||
|
0c0fad719f | ||
|
b75c6fe4e7 | ||
|
d9fcbeec72 | ||
|
5d79721b6d | ||
|
4be3592cf4 | ||
|
b4cdcbbbd7 | ||
|
e9c7f4be06 | ||
|
0530f9172c | ||
|
e1e55c4b2a | ||
|
c6f7ff70f8 | ||
|
02aaabdc4e | ||
|
0f673947af | ||
|
f35f62fdf3 | ||
|
68474e75d5 | ||
|
eaf75cedb0 | ||
|
fe8b9f9e91 | ||
|
07d3745747 | ||
|
44cf5ca264 | ||
|
44d10c2390 | ||
|
9698821828 | ||
|
9f8500fc7e | ||
|
e5dbbf2eaa | ||
|
959e5402b8 | ||
|
492c9324d2 | ||
|
42c159605d | ||
|
78c427f38e | ||
|
bae74d1693 | ||
|
123f40f39e | ||
|
e3129cec74 | ||
|
5508c1e084 | ||
|
9f2893319a | ||
|
125a39fb5b | ||
|
4ca1832732 | ||
|
3609bfea4d | ||
|
94a5d84fae | ||
|
85e691fcbe | ||
|
709ccff8fa | ||
|
6c7eb4609d | ||
|
2c69b08eae | ||
|
84e7591d05 | ||
|
545e8578c9 | ||
|
f1d073d053 | ||
|
20b3396ec2 | ||
|
b305be82ae | ||
|
1430d2c236 | ||
|
acf8ade23c | ||
|
aa884b49ce | ||
|
acb736e44f | ||
|
180886c588 | ||
|
e993476fad | ||
|
efaddeebb4 | ||
|
93f623a13a | ||
|
87a40cff61 | ||
|
f00c9b9a03 | ||
|
20b2ae436c | ||
|
820a4640da | ||
|
0193853035 | ||
|
b40344675e | ||
|
df7e687f61 | ||
|
ad10d26f13 | ||
|
588c64b380 | ||
|
3d3efc5e82 | ||
|
d8a20ebbd9 | ||
|
ebce8827ea |
92
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
@@ -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?
|
||||
|
||||
|
@@ -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
|
||||
|
13
README.md
@@ -15,8 +15,9 @@
|
||||
|
||||
<h3 align="center">
|
||||
<a href="https://discord.gg/QeFwyWSKwC">Community</a> •
|
||||
<a href="https://www.chartdb.io">Website</a> •
|
||||
<a href="https://app.chartdb.io/examples">Demo</a>
|
||||
<a href="https://www.chartdb.io?ref=github_readme">Website</a> •
|
||||
<a href="https://chartdb.io/templates?ref=github_readme">Examples</a> •
|
||||
<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
@@ -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
@@ -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"
|
||||
}
|
||||
|
188
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 882 KiB After Width: | Height: | Size: 882 KiB |
BIN
src/assets/templates/adonis-acl-dark.png
Normal file
After Width: | Height: | Size: 290 KiB |
BIN
src/assets/templates/adonis-acl.png
Normal file
After Width: | Height: | Size: 322 KiB |
BIN
src/assets/templates/airbnb-dark.png
Normal file
After Width: | Height: | Size: 388 KiB |
BIN
src/assets/templates/airbnb.png
Normal file
After Width: | Height: | Size: 424 KiB |
BIN
src/assets/templates/akaunting-dark.png
Normal file
After Width: | Height: | Size: 327 KiB |
BIN
src/assets/templates/akaunting.png
Normal file
After Width: | Height: | Size: 345 KiB |
BIN
src/assets/templates/attendize-db-dark.png
Normal file
After Width: | Height: | Size: 525 KiB |
BIN
src/assets/templates/attendize-db.png
Normal file
After Width: | Height: | Size: 613 KiB |
BIN
src/assets/templates/bookstack-db-dark.png
Normal file
After Width: | Height: | Size: 452 KiB |
BIN
src/assets/templates/bookstack-db.png
Normal file
After Width: | Height: | Size: 578 KiB |
BIN
src/assets/templates/bouncer-db-dark.png
Normal file
After Width: | Height: | Size: 323 KiB |
BIN
src/assets/templates/bouncer-db.png
Normal file
After Width: | Height: | Size: 348 KiB |
BIN
src/assets/templates/buddypress-dark.png
Normal file
After Width: | Height: | Size: 417 KiB |
BIN
src/assets/templates/buddypress.png
Normal file
After Width: | Height: | Size: 496 KiB |
BIN
src/assets/templates/cabot-db-dark.png
Normal file
After Width: | Height: | Size: 420 KiB |
BIN
src/assets/templates/cabot-db.png
Normal file
After Width: | Height: | Size: 498 KiB |
BIN
src/assets/templates/comfortable-mexican-sofa-db-dark.png
Normal file
After Width: | Height: | Size: 375 KiB |
BIN
src/assets/templates/comfortable-mexican-sofa-db.png
Normal file
After Width: | Height: | Size: 409 KiB |
BIN
src/assets/templates/django-db-dark.png
Normal file
After Width: | Height: | Size: 354 KiB |
BIN
src/assets/templates/django-db.png
Normal file
After Width: | Height: | Size: 389 KiB |
BIN
src/assets/templates/feedbin-db-dark.png
Normal file
After Width: | Height: | Size: 498 KiB |
BIN
src/assets/templates/feedbin-db.png
Normal file
After Width: | Height: | Size: 583 KiB |
BIN
src/assets/templates/flarum-db-dark.png
Normal file
After Width: | Height: | Size: 412 KiB |
BIN
src/assets/templates/flarum-db.png
Normal file
After Width: | Height: | Size: 499 KiB |
BIN
src/assets/templates/freescout-db-dark.png
Normal file
After Width: | Height: | Size: 441 KiB |
BIN
src/assets/templates/freescout-db.png
Normal file
After Width: | Height: | Size: 502 KiB |
BIN
src/assets/templates/gravity-db-dark.png
Normal file
After Width: | Height: | Size: 382 KiB |
BIN
src/assets/templates/gravity-db.png
Normal file
After Width: | Height: | Size: 415 KiB |
BIN
src/assets/templates/hacker-news-db-dark.png
Normal file
After Width: | Height: | Size: 427 KiB |
BIN
src/assets/templates/hacker-news-db.png
Normal file
After Width: | Height: | Size: 472 KiB |
BIN
src/assets/templates/koel-db-dark.png
Normal file
After Width: | Height: | Size: 369 KiB |
BIN
src/assets/templates/koel-db.png
Normal file
After Width: | Height: | Size: 402 KiB |
BIN
src/assets/templates/laravel-db-dark.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
src/assets/templates/laravel-db.png
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
src/assets/templates/laravel-permission-db-dark.png
Normal file
After Width: | Height: | Size: 215 KiB |
BIN
src/assets/templates/laravel-permission-db.png
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
src/assets/templates/laravel-spark-db-dark.png
Normal file
After Width: | Height: | Size: 412 KiB |
BIN
src/assets/templates/laravel-spark-db.png
Normal file
After Width: | Height: | Size: 449 KiB |
BIN
src/assets/templates/lobsters-db-dark.png
Normal file
After Width: | Height: | Size: 403 KiB |
BIN
src/assets/templates/lobsters-db.png
Normal file
After Width: | Height: | Size: 450 KiB |
BIN
src/assets/templates/monica-db-dark.png
Normal file
After Width: | Height: | Size: 746 KiB |
BIN
src/assets/templates/monica-db.png
Normal file
After Width: | Height: | Size: 846 KiB |
BIN
src/assets/templates/pokemon-dark.png
Normal file
After Width: | Height: | Size: 421 KiB |
BIN
src/assets/templates/pokemon.png
Normal file
After Width: | Height: | Size: 444 KiB |
BIN
src/assets/templates/refinerycms-db-dark.png
Normal file
After Width: | Height: | Size: 414 KiB |
BIN
src/assets/templates/refinerycms-db.png
Normal file
After Width: | Height: | Size: 434 KiB |
BIN
src/assets/templates/saas-pegasus-db-dark.png
Normal file
After Width: | Height: | Size: 474 KiB |
BIN
src/assets/templates/saas-pegasus-db.png
Normal file
After Width: | Height: | Size: 500 KiB |
BIN
src/assets/templates/snipe-it-db-dark.png
Normal file
After Width: | Height: | Size: 383 KiB |
BIN
src/assets/templates/snipe-it-db.png
Normal file
After Width: | Height: | Size: 428 KiB |
BIN
src/assets/templates/sylius-db-dark.png
Normal file
After Width: | Height: | Size: 673 KiB |
BIN
src/assets/templates/sylius-db.png
Normal file
After Width: | Height: | Size: 804 KiB |
BIN
src/assets/templates/ticketit-db-dark.png
Normal file
After Width: | Height: | Size: 337 KiB |
BIN
src/assets/templates/ticketit-db.png
Normal file
After Width: | Height: | Size: 374 KiB |
BIN
src/assets/templates/twitter-db-dark.png
Normal file
After Width: | Height: | Size: 370 KiB |
BIN
src/assets/templates/twitter-db.png
Normal file
After Width: | Height: | Size: 404 KiB |
BIN
src/assets/templates/voyager-db-dark.png
Normal file
After Width: | Height: | Size: 401 KiB |
BIN
src/assets/templates/voyager-db.png
Normal file
After Width: | Height: | Size: 431 KiB |
BIN
src/assets/templates/wordpress-db-dark.png
Normal file
After Width: | Height: | Size: 461 KiB |
BIN
src/assets/templates/wordpress-db.png
Normal file
After Width: | Height: | Size: 489 KiB |
62
src/components/alert/alert.tsx
Normal 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 };
|
@@ -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>
|
||||
|
@@ -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,
|
||||
};
|
||||
|
168
src/components/file-uploader/file-uploader.tsx
Normal 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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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' : ''}`,
|
||||
|
@@ -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 () => {
|
||||
|
@@ -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,
|
||||
});
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
@@ -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={{}}>
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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 ? (
|
||||
|
@@ -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
|
||||
|
132
src/dialogs/export-diagram-dialog/export-diagram-dialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
134
src/dialogs/import-diagram-dialog/import-diagram-dialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
|
128
src/globals.css
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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 };
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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
@@ -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',
|
||||
};
|
@@ -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',
|
||||
};
|
||||
|