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
|
# Changelog
|
||||||
|
|
||||||
|
## [1.3.0](https://github.com/chartdb/chartdb/compare/v1.2.0...v1.3.0) (2024-11-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **side panel:** collapsible side panel on desktop view + keyboard shortcut ([#439](https://github.com/chartdb/chartdb/issues/439)) ([70f545f](https://github.com/chartdb/chartdb/commit/70f545f78bab9c510a6e5936fa5b259b806b6c69))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **dialogs:** fix height of dialogs for small screens ([#440](https://github.com/chartdb/chartdb/issues/440)) ([667685e](https://github.com/chartdb/chartdb/commit/667685ed0f6a8cc61ae86b3ba60e052fbe6a9e1a))
|
||||||
|
* **drawer:** set fix min size ([#429](https://github.com/chartdb/chartdb/issues/429)) ([c5e0ea6](https://github.com/chartdb/chartdb/commit/c5e0ea6fa4017666ff3bc1e3071c487df48afd3d))
|
||||||
|
* **export-sql:** add unique to export script ([#422](https://github.com/chartdb/chartdb/issues/422)) ([b75c6fe](https://github.com/chartdb/chartdb/commit/b75c6fe4e78f3e2058be680f2fa0442db3b4a6bd))
|
||||||
|
* fix layout warnings ([#434](https://github.com/chartdb/chartdb/issues/434)) ([94ec43b](https://github.com/chartdb/chartdb/commit/94ec43b60845bb8c3592ce1b1450ca0171a53f99))
|
||||||
|
* **i18n:** add bahasa indonesia translation ([#331](https://github.com/chartdb/chartdb/issues/331)) ([ab07da0](https://github.com/chartdb/chartdb/commit/ab07da0b031f0d4050ff6b44ddcb94cb6c0010b6))
|
||||||
|
* **i18n:** add missing type to vi.ts ([#444](https://github.com/chartdb/chartdb/issues/444)) ([e77ee60](https://github.com/chartdb/chartdb/commit/e77ee60a5b47e0854d11b0ee2f16d6956737d0ff))
|
||||||
|
* **i18n:** Add Telugu Language ([#352](https://github.com/chartdb/chartdb/issues/352)) ([8749591](https://github.com/chartdb/chartdb/commit/8749591be036e131de4bfeed1e6eece8d62980dd))
|
||||||
|
* **i18n:** add Turkish translations ([#315](https://github.com/chartdb/chartdb/issues/315)) ([d9fcbee](https://github.com/chartdb/chartdb/commit/d9fcbeec726b7bde9f7d202bf09dc6b617e3ad80))
|
||||||
|
* **i18n:** add Vietnamese translations ([#435](https://github.com/chartdb/chartdb/issues/435)) ([6c65c2e](https://github.com/chartdb/chartdb/commit/6c65c2e9cce600b9778b84ce5b5f1625dc6f1a58))
|
||||||
|
* **i18n:** Translating to Gujarati language ([#433](https://github.com/chartdb/chartdb/issues/433)) ([2940431](https://github.com/chartdb/chartdb/commit/2940431efa1a6aa54d80c61d5e05f0ad47cd67ba))
|
||||||
|
* **i18n:** Translation of the export error into Russian ([#418](https://github.com/chartdb/chartdb/issues/418)) ([7c3c628](https://github.com/chartdb/chartdb/commit/7c3c62860efc98d3aabf2132a79ac945ffc8315a))
|
||||||
|
* **i18n:** update korean for 1.2.0 ([#419](https://github.com/chartdb/chartdb/issues/419)) ([8397bef](https://github.com/chartdb/chartdb/commit/8397bef3924610d94661aae99c55ba4fa376a186))
|
||||||
|
* **import script:** remove double quotes ([#442](https://github.com/chartdb/chartdb/issues/442)) ([fb702c8](https://github.com/chartdb/chartdb/commit/fb702c87ce5254bf6e0209c692305f5086956090))
|
||||||
|
* **share:** fix export to handle broken indexes & relationships ([#416](https://github.com/chartdb/chartdb/issues/416)) ([4be3592](https://github.com/chartdb/chartdb/commit/4be3592cf4d160be83ddf1db01ffe9afdef119fa))
|
||||||
|
* **templates:** add Five more templates (bouncer, cabot, feedbin, Pythonic, flarum, freescout) ([#441](https://github.com/chartdb/chartdb/issues/441)) ([eaa0678](https://github.com/chartdb/chartdb/commit/eaa067814fd96fcc1ee10488ee747a71a8e8ec7a))
|
||||||
|
|
||||||
|
## [1.2.0](https://github.com/chartdb/chartdb/compare/v1.1.0...v1.2.0) (2024-11-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **duplicate table:** duplicate table from the canvas and sidebar ([#404](https://github.com/chartdb/chartdb/issues/404)) ([44cf5ca](https://github.com/chartdb/chartdb/commit/44cf5ca264f52851f2dffb51a752a52b6fa7ec8d))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **AI exports:** add cahching layer to SQL exports ([#390](https://github.com/chartdb/chartdb/issues/390)) ([e5dbbf2](https://github.com/chartdb/chartdb/commit/e5dbbf2eaab6d80a531d451211b6f5a415bc7ce3))
|
||||||
|
* **canvas:** fix auto zoom on diagram load ([#395](https://github.com/chartdb/chartdb/issues/395)) ([492c932](https://github.com/chartdb/chartdb/commit/492c9324d27b561470c4967ce2e99f82eec467d8))
|
||||||
|
* **dockerfile:** support SPA refresh to resolve nginx return 404 ([#384](https://github.com/chartdb/chartdb/issues/384)) ([eaf75ce](https://github.com/chartdb/chartdb/commit/eaf75cedb0e024236c7684bb533856d7f80074da))
|
||||||
|
* **docs:** update license reference ([#403](https://github.com/chartdb/chartdb/issues/403)) ([44d10c2](https://github.com/chartdb/chartdb/commit/44d10c23907165288951a9d2ec3165ad23f81c61))
|
||||||
|
* **export image:** Add support for displaying cardinality relationships + background ([#407](https://github.com/chartdb/chartdb/issues/407)) ([68474e7](https://github.com/chartdb/chartdb/commit/68474e75d56ed4b4b445cc9b7f59cca96a4ca5db))
|
||||||
|
* **i18n:** add Nepali translations ([#406](https://github.com/chartdb/chartdb/issues/406)) ([e1e55c4](https://github.com/chartdb/chartdb/commit/e1e55c4b2ac7755b0810dc1f21da44903fe68a54))
|
||||||
|
* **i18n:** change language keeps selected language also after refreshing the page ([#409](https://github.com/chartdb/chartdb/issues/409)) ([f35f62f](https://github.com/chartdb/chartdb/commit/f35f62fdf38ca84065f171a31b80aa8123b1d8b9))
|
||||||
|
* **i18n:** Create Translations in Marathi language ([#266](https://github.com/chartdb/chartdb/issues/266)) ([c6f7ff7](https://github.com/chartdb/chartdb/commit/c6f7ff70f841efb9cf1766338f409fe0ea7bb998))
|
||||||
|
* **i18n:** fix language nav: close when lang selected, hide tooltip when lang selected ([#411](https://github.com/chartdb/chartdb/issues/411)) ([02aaabd](https://github.com/chartdb/chartdb/commit/02aaabdc4e9b1570d81ff03fe1e6da0307f22999))
|
||||||
|
* **templates:** add five more templates (Sylius, Monica, Attendize, SaaS Pegasus & BookStack) ([#408](https://github.com/chartdb/chartdb/issues/408)) ([0f67394](https://github.com/chartdb/chartdb/commit/0f673947af469e86f70737427ac8fb3c2420d1a2))
|
||||||
|
* **templates:** add six more templates (ticketit, snipe-it, refinerycms, comfortable-mexican-sofa, buddypress, lobsters) ([#402](https://github.com/chartdb/chartdb/issues/402)) ([07d3745](https://github.com/chartdb/chartdb/commit/07d374574775d132e1cba0908c47dcbbd6cd2c3f))
|
||||||
|
* **templates:** fix cloned indexes from a template ([#398](https://github.com/chartdb/chartdb/issues/398)) ([9f8500f](https://github.com/chartdb/chartdb/commit/9f8500fc7e36e6a819ecb9029f263d80eac88279))
|
||||||
|
* **templates:** fix tags urls ([#405](https://github.com/chartdb/chartdb/issues/405)) ([fe8b9f9](https://github.com/chartdb/chartdb/commit/fe8b9f9e91481d8a3272113b6f4be4da8d61ad04))
|
||||||
|
* **templates:** tag urls lowercase to support browsers ([#397](https://github.com/chartdb/chartdb/issues/397)) ([959e540](https://github.com/chartdb/chartdb/commit/959e5402b8c112fae6243ce9283947057506c128))
|
||||||
|
|
||||||
|
## [1.1.0](https://github.com/chartdb/chartdb/compare/v1.0.1...v1.1.0) (2024-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **add templates:** add five more templates (laravel, django, twitter… ([#371](https://github.com/chartdb/chartdb/issues/371)) ([20b3396](https://github.com/chartdb/chartdb/commit/20b3396ec2afff09ca8bcdd91f5c6284c93cd959))
|
||||||
|
* **canvas:** Added Snap to grid functionality. Toggle/hold shift to enable snap to grid. ([#373](https://github.com/chartdb/chartdb/issues/373)) ([6c7eb46](https://github.com/chartdb/chartdb/commit/6c7eb4609d8466278de30317665929ec529c1f94))
|
||||||
|
* **share:** add sharing capabilities to import and export diagrams ([#365](https://github.com/chartdb/chartdb/issues/365)) ([94a5d84](https://github.com/chartdb/chartdb/commit/94a5d84fae819b0de6c1e471d1aad16dc8f39dd6))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bundle:** fix bundle size ([#382](https://github.com/chartdb/chartdb/issues/382)) ([4ca1832](https://github.com/chartdb/chartdb/commit/4ca18327324106950f0d1af851b9b74379b67b7b))
|
||||||
|
* **dockerfile:** support openai key in docker build ([#366](https://github.com/chartdb/chartdb/issues/366)) ([545e857](https://github.com/chartdb/chartdb/commit/545e8578c9e8aa71696f6aa8bec81cacaa602c2d))
|
||||||
|
* **i18n:** add korean ([#362](https://github.com/chartdb/chartdb/issues/362)) ([b305be8](https://github.com/chartdb/chartdb/commit/b305be82aee00994ef576ca6fd62d72dd491f771))
|
||||||
|
* **i18n:** Add simplified chinese ([#385](https://github.com/chartdb/chartdb/issues/385)) ([9f28933](https://github.com/chartdb/chartdb/commit/9f2893319a1a2aed9a7c03d15e25a17ab37c2465))
|
||||||
|
* **i18n:** Added Russian language ([#376](https://github.com/chartdb/chartdb/issues/376)) ([2c69b08](https://github.com/chartdb/chartdb/commit/2c69b08eaea6b86ce0c1ddb18a23e22629198bf5))
|
||||||
|
* **i18n:** added traditional Chinese language translation ([#356](https://github.com/chartdb/chartdb/issues/356)) ([123f40f](https://github.com/chartdb/chartdb/commit/123f40f39e703ad612635964af530ac72c387d3c))
|
||||||
|
* **i18n:** Fixed part of RU lang introduced in [#365](https://github.com/chartdb/chartdb/issues/365) feat(share) ([#380](https://github.com/chartdb/chartdb/issues/380)) ([5508c1e](https://github.com/chartdb/chartdb/commit/5508c1e084e0ee24d1a54f721f760b9fc14df107))
|
||||||
|
* **i18n:** french translation update - share menu ([#391](https://github.com/chartdb/chartdb/issues/391)) ([e3129ce](https://github.com/chartdb/chartdb/commit/e3129cec744d18f09953544d9e74cd5adc4e8afb))
|
||||||
|
* **import json:** for Check Script Result, default with quotes ([#358](https://github.com/chartdb/chartdb/issues/358)) ([1430d2c](https://github.com/chartdb/chartdb/commit/1430d2c2365b7b74e36b8ff9d32a163d7437448a))
|
||||||
|
* improve title name edit interaction ([#367](https://github.com/chartdb/chartdb/issues/367)) ([84e7591](https://github.com/chartdb/chartdb/commit/84e7591d0586b9a457f31737c6e363ef41574142))
|
||||||
|
* **share:** add loader to the export ([#381](https://github.com/chartdb/chartdb/issues/381)) ([3609bfe](https://github.com/chartdb/chartdb/commit/3609bfea4d4c78b03711ff8d721b4e67bf82185a))
|
||||||
|
* **sql export:** make loading for export interactive ([#388](https://github.com/chartdb/chartdb/issues/388)) ([125a39f](https://github.com/chartdb/chartdb/commit/125a39fb5be803f0e6db0b68fb5bc8e290fa8dae))
|
||||||
|
* **templates:** change the template url to be database instead of db ([#374](https://github.com/chartdb/chartdb/issues/374)) ([f1d073d](https://github.com/chartdb/chartdb/commit/f1d073d05383955da6f60a9a66ed2be879b103e4))
|
||||||
|
* **templates:** fix issue with double-clone on localhost ([#394](https://github.com/chartdb/chartdb/issues/394)) ([78c427f](https://github.com/chartdb/chartdb/commit/78c427f38e5c64fc340d13ceb2153c2b85db437e))
|
||||||
|
|
||||||
|
## [1.0.1](https://github.com/chartdb/chartdb/compare/v1.0.0...v1.0.1) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **offline:** add support when running on isolated network ([#359](https://github.com/chartdb/chartdb/issues/359)) ([aa884b4](https://github.com/chartdb/chartdb/commit/aa884b49ce16d70f67881bdc940993c1fe901796))
|
||||||
|
* open default diagram after deleting current diagram ([#350](https://github.com/chartdb/chartdb/issues/350)) ([87a40cf](https://github.com/chartdb/chartdb/commit/87a40cff615b04b678642ba2d6e097c38b26d239))
|
||||||
|
* **select-box:** allow using tab & space to show choices ([#336](https://github.com/chartdb/chartdb/issues/336)) ([93f623a](https://github.com/chartdb/chartdb/commit/93f623a13a61e9143638fbe7e8346f07e37a26b2))
|
||||||
|
* **smart query:** import postgres FKs ([#357](https://github.com/chartdb/chartdb/issues/357)) ([acb736e](https://github.com/chartdb/chartdb/commit/acb736e44fd50d29a85b4eff42e20780aef710ed))
|
||||||
|
* **templates:** add two more templates (Airbnb, Wordpress) ([#317](https://github.com/chartdb/chartdb/issues/317)) ([ebce882](https://github.com/chartdb/chartdb/commit/ebce8827eab049eefa0eebcb0ec2540698bc0e15))
|
||||||
|
* **templates:** align database icon ([#351](https://github.com/chartdb/chartdb/issues/351)) ([efaddee](https://github.com/chartdb/chartdb/commit/efaddeebb4f24235d82f4e2bf7423fbf48b97187))
|
||||||
|
* **template:** separator in case of empty url ([#355](https://github.com/chartdb/chartdb/issues/355)) ([180886c](https://github.com/chartdb/chartdb/commit/180886c5882f2329c797fc284b255012d21f5b5c))
|
||||||
|
* **templates:** fetch templates data from router ([#321](https://github.com/chartdb/chartdb/issues/321)) ([d8a20eb](https://github.com/chartdb/chartdb/commit/d8a20ebbd9118989690a40fcd3aa59fb156b446f))
|
||||||
|
|
||||||
## 1.0.0 (2024-11-04)
|
## 1.0.0 (2024-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ To get started:
|
|||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
By contributing, you agree that your work will be licensed under ChartDB's [license](https://github.com/chartdb/chartdb/blob/main/LICENSE.md).
|
By contributing, you agree that your work will be licensed under ChartDB's [license](https://github.com/chartdb/chartdb/blob/main/LICENSE).
|
||||||
|
|
||||||
## Questions?
|
## Questions?
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
ARG VITE_OPENAI_API_KEY
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
@@ -14,6 +16,7 @@ RUN npm run build
|
|||||||
FROM nginx:stable-alpine AS production
|
FROM nginx:stable-alpine AS production
|
||||||
|
|
||||||
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
|
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
|
||||||
|
COPY ./default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
# Expose the default port for the Nginx web server
|
# Expose the default port for the Nginx web server
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
13
README.md
@@ -15,8 +15,9 @@
|
|||||||
|
|
||||||
<h3 align="center">
|
<h3 align="center">
|
||||||
<a href="https://discord.gg/QeFwyWSKwC">Community</a> •
|
<a href="https://discord.gg/QeFwyWSKwC">Community</a> •
|
||||||
<a href="https://www.chartdb.io">Website</a> •
|
<a href="https://www.chartdb.io?ref=github_readme">Website</a> •
|
||||||
<a href="https://app.chartdb.io/examples">Demo</a>
|
<a href="https://chartdb.io/templates?ref=github_readme">Examples</a> •
|
||||||
|
<a href="https://app.chartdb.io?ref=github_readme">Demo</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<h4 align="center">
|
<h4 align="center">
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width='700px' src="./public/ChartDB.png">
|
<img width='700px' src="./public/chartdb.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### 🎉 ChartDB
|
### 🎉 ChartDB
|
||||||
@@ -71,7 +72,7 @@ ChartDB is currently in Public Beta. Star and watch this repository to get notif
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Use the [cloud version](https://app.chartdb.io/) or deploy locally:
|
Use the [cloud version](https://app.chartdb.io?ref=github_readme_2) or deploy locally:
|
||||||
|
|
||||||
### How To Use
|
### How To Use
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
|
|||||||
### Running the Docker Container
|
### Running the Docker Container
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t chartdb .
|
docker build -t chartdb . (If you want AI capabilities, use `docker build --build-arg VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -t chartdb .`)
|
||||||
docker run -p 8080:80 chartdb
|
docker run -p 8080:80 chartdb
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ Open your browser and navigate to `http://localhost:8080`.
|
|||||||
|
|
||||||
## Try it on our website
|
## Try it on our website
|
||||||
|
|
||||||
1. Go to [ChartDB.io](https://chartdb.io)
|
1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)
|
||||||
2. Click "Go to app"
|
2. Click "Go to app"
|
||||||
3. Choose the database that you are using.
|
3. Choose the database that you are using.
|
||||||
4. Take the magic query and run it in your database.
|
4. Take the magic query and run it in your database.
|
||||||
|
|||||||
15
default.conf
Normal file
@@ -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",
|
"name": "chartdb",
|
||||||
"version": "1.0.0",
|
"version": "1.3.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"version": "1.0.0",
|
"version": "1.3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.51",
|
"@ai-sdk/openai": "^0.0.51",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^23.14.0",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"lucide-react": "^0.441.0",
|
"lucide-react": "^0.441.0",
|
||||||
"monaco-editor": "^0.52.0",
|
"monaco-editor": "^0.52.0",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
@@ -60,7 +61,8 @@
|
|||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"timeago-react": "^3.0.6",
|
"timeago-react": "^3.0.6",
|
||||||
"vaul": "^0.9.1"
|
"vaul": "^0.9.1",
|
||||||
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.1.0",
|
"@types/node": "^22.1.0",
|
||||||
@@ -6754,6 +6756,15 @@
|
|||||||
"@babel/runtime": "^7.23.2"
|
"@babel/runtime": "^7.23.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -10539,7 +10550,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
188
package.json
@@ -1,95 +1,97 @@
|
|||||||
{
|
{
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "npm run lint && tsc -b && vite build",
|
"build": "npm run lint && tsc -b && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.51",
|
"@ai-sdk/openai": "^0.0.51",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-avatar": "^1.1.0",
|
"@radix-ui/react-avatar": "^1.1.0",
|
||||||
"@radix-ui/react-checkbox": "^1.1.1",
|
"@radix-ui/react-checkbox": "^1.1.1",
|
||||||
"@radix-ui/react-collapsible": "^1.1.0",
|
"@radix-ui/react-collapsible": "^1.1.0",
|
||||||
"@radix-ui/react-context-menu": "^2.2.1",
|
"@radix-ui/react-context-menu": "^2.2.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-hover-card": "^1.1.1",
|
"@radix-ui/react-hover-card": "^1.1.1",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-menubar": "^1.1.1",
|
"@radix-ui/react-menubar": "^1.1.1",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||||
"@radix-ui/react-select": "^2.1.1",
|
"@radix-ui/react-select": "^2.1.1",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.0",
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"@uidotdev/usehooks": "^2.4.1",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"@xyflow/react": "^12.3.1",
|
"@xyflow/react": "^12.3.1",
|
||||||
"ahooks": "^3.8.1",
|
"ahooks": "^3.8.1",
|
||||||
"ai": "^3.3.14",
|
"ai": "^3.3.14",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"dexie": "^4.0.8",
|
"dexie": "^4.0.8",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^23.14.0",
|
||||||
"lucide-react": "^0.441.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"monaco-editor": "^0.52.0",
|
"lucide-react": "^0.441.0",
|
||||||
"nanoid": "^5.0.7",
|
"monaco-editor": "^0.52.0",
|
||||||
"node-sql-parser": "^5.3.2",
|
"nanoid": "^5.0.7",
|
||||||
"react": "^18.3.1",
|
"node-sql-parser": "^5.3.2",
|
||||||
"react-dom": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-helmet-async": "^2.0.5",
|
"react-dom": "^18.3.1",
|
||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-helmet-async": "^2.0.5",
|
||||||
"react-i18next": "^15.0.1",
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"react-resizable-panels": "^2.0.22",
|
"react-i18next": "^15.0.1",
|
||||||
"react-responsive": "^10.0.0",
|
"react-resizable-panels": "^2.0.22",
|
||||||
"react-router-dom": "^6.26.0",
|
"react-responsive": "^10.0.0",
|
||||||
"react-use": "^17.5.1",
|
"react-router-dom": "^6.26.0",
|
||||||
"tailwind-merge": "^2.4.0",
|
"react-use": "^17.5.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwind-merge": "^2.4.0",
|
||||||
"timeago-react": "^3.0.6",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^0.9.1"
|
"timeago-react": "^3.0.6",
|
||||||
},
|
"vaul": "^0.9.1",
|
||||||
"devDependencies": {
|
"zod": "^3.23.8"
|
||||||
"@types/node": "^22.1.0",
|
},
|
||||||
"@types/react": "^18.3.3",
|
"devDependencies": {
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/node": "^22.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
"@types/react": "^18.3.3",
|
||||||
"@typescript-eslint/parser": "^7.15.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"@typescript-eslint/parser": "^7.15.0",
|
||||||
"eslint": "^8.57.0",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"eslint-plugin-react": "^7.35.0",
|
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.7",
|
"eslint-plugin-react": "^7.35.0",
|
||||||
"eslint-plugin-tailwindcss": "^3.17.4",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"husky": "^9.1.5",
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
"postcss": "^8.4.40",
|
"eslint-plugin-tailwindcss": "^3.17.4",
|
||||||
"prettier": "^3.3.3",
|
"husky": "^9.1.5",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"postcss": "^8.4.40",
|
||||||
"tailwindcss": "^3.4.7",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "^5.2.2",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"unplugin-inject-preload": "^3.0.0",
|
"tailwindcss": "^3.4.7",
|
||||||
"vite": "^5.3.4"
|
"typescript": "^5.2.2",
|
||||||
}
|
"unplugin-inject-preload": "^3.0.0",
|
||||||
|
"vite": "^5.3.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { DarkTheme } from './themes/dark';
|
import { DarkTheme } from './themes/dark';
|
||||||
import { LightTheme } from './themes/light';
|
import { LightTheme } from './themes/light';
|
||||||
|
import './config.ts';
|
||||||
|
|
||||||
export interface CodeSnippetProps {
|
export interface CodeSnippetProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
code: string;
|
code: string;
|
||||||
language?: 'sql' | 'shell';
|
language?: 'sql' | 'shell';
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
autoScroll?: boolean;
|
||||||
|
isComplete?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Editor = lazy(() =>
|
export const Editor = lazy(() =>
|
||||||
@@ -24,7 +27,14 @@ export const Editor = lazy(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||||
({ className, code, loading, language = 'sql' }) => {
|
({
|
||||||
|
className,
|
||||||
|
code,
|
||||||
|
loading,
|
||||||
|
language = 'sql',
|
||||||
|
autoScroll = false,
|
||||||
|
isComplete = true,
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const monaco = useMonaco();
|
const monaco = useMonaco();
|
||||||
const { effectiveTheme } = useTheme();
|
const { effectiveTheme } = useTheme();
|
||||||
@@ -46,6 +56,16 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
}, [isCopied]);
|
}, [isCopied]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (monaco) {
|
||||||
|
const editor = monaco.editor.getModels()[0];
|
||||||
|
if (editor && autoScroll) {
|
||||||
|
const lineCount = editor.getLineCount();
|
||||||
|
monaco.editor.getEditors()[0]?.revealLine(lineCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [code, monaco, autoScroll]);
|
||||||
|
|
||||||
const copyToClipboard = useCallback(() => {
|
const copyToClipboard = useCallback(() => {
|
||||||
navigator.clipboard.writeText(code);
|
navigator.clipboard.writeText(code);
|
||||||
setIsCopied(true);
|
setIsCopied(true);
|
||||||
@@ -62,32 +82,38 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
<Suspense fallback={<Spinner />}>
|
<Suspense fallback={<Spinner />}>
|
||||||
<Tooltip
|
{isComplete ? (
|
||||||
onOpenChange={setTooltipOpen}
|
<Tooltip
|
||||||
open={isCopied || tooltipOpen}
|
onOpenChange={setTooltipOpen}
|
||||||
>
|
open={isCopied || tooltipOpen}
|
||||||
<TooltipTrigger
|
|
||||||
asChild
|
|
||||||
className="absolute right-1 top-1 z-10"
|
|
||||||
>
|
>
|
||||||
<span>
|
<TooltipTrigger
|
||||||
<Button
|
asChild
|
||||||
className=" h-fit p-1.5"
|
className="absolute right-1 top-1 z-10"
|
||||||
variant="outline"
|
>
|
||||||
onClick={copyToClipboard}
|
<span>
|
||||||
>
|
<Button
|
||||||
{isCopied ? (
|
className=" h-fit p-1.5"
|
||||||
<CopyCheck size={16} />
|
variant="outline"
|
||||||
) : (
|
onClick={copyToClipboard}
|
||||||
<Copy size={16} />
|
>
|
||||||
)}
|
{isCopied ? (
|
||||||
</Button>
|
<CopyCheck size={16} />
|
||||||
</span>
|
) : (
|
||||||
</TooltipTrigger>
|
<Copy size={16} />
|
||||||
<TooltipContent>
|
)}
|
||||||
{t(isCopied ? 'copied' : 'copy_to_clipboard')}
|
</Button>
|
||||||
</TooltipContent>
|
</span>
|
||||||
</Tooltip>
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{t(
|
||||||
|
isCopied
|
||||||
|
? 'copied'
|
||||||
|
: 'copy_to_clipboard'
|
||||||
|
)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Editor
|
<Editor
|
||||||
value={code}
|
value={code}
|
||||||
@@ -117,6 +143,9 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
contextmenu: false,
|
contextmenu: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{!isComplete ? (
|
||||||
|
<div className="absolute bottom-2 right-2 size-2 animate-blink rounded-full bg-pink-600" />
|
||||||
|
) : null}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|||||||
import { Cross2Icon } from '@radix-ui/react-icons';
|
import { Cross2Icon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { ScrollArea } from '../scroll-area/scroll-area';
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root;
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
@@ -110,6 +111,18 @@ const DialogDescription = React.forwardRef<
|
|||||||
));
|
));
|
||||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
const DialogInternalContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollArea>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollArea>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ScrollArea
|
||||||
|
ref={ref}
|
||||||
|
className={cn('flex max-h-screen flex-col overflow-y-auto', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogInternalContent.displayName = 'DialogInternalContent';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
@@ -121,4 +134,5 @@ export {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
DialogInternalContent,
|
||||||
};
|
};
|
||||||
|
|||||||
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}
|
strokeWidth={item.selected ? 2.4 : 2}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{item.title}
|
<span className="min-w-0 truncate">{item.title}</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const ScrollArea = React.forwardRef<
|
|||||||
className={cn('relative overflow-hidden', className)}
|
className={cn('relative overflow-hidden', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport className="scrollable-flex size-full rounded-[inherit]">
|
<ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]">
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
@@ -40,7 +40,7 @@ const ScrollBar = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative z-20 flex-1 rounded-full bg-border" />
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
));
|
));
|
||||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export interface SelectBoxOption {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectBoxProps {
|
export interface SelectBoxProps {
|
||||||
options: SelectBoxOption[];
|
options: SelectBoxOption[];
|
||||||
value?: string[] | string;
|
value?: string[] | string;
|
||||||
onChange?: (values: string[] | string) => void;
|
onChange?: (values: string[] | string) => void;
|
||||||
@@ -156,9 +156,19 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
[options, value, multiple]
|
[options, value, multiple]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = React.useCallback(
|
||||||
|
(e: React.KeyboardEvent) => {
|
||||||
|
if (!isOpen && e.code.toLowerCase() === 'space') {
|
||||||
|
e.preventDefault();
|
||||||
|
onOpenChange(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isOpen, onOpenChange]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={isOpen} onOpenChange={onOpenChange} modal={true}>
|
<Popover open={isOpen} onOpenChange={onOpenChange} modal={true}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`,
|
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`,
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import type { DBRelationship } from '@/lib/domain/db-relationship';
|
|||||||
import { useStorage } from '@/hooks/use-storage';
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { useConfig } from '@/hooks/use-config';
|
|
||||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
import type { DBSchema } from '@/lib/domain/db-schema';
|
import type { DBSchema } from '@/lib/domain/db-schema';
|
||||||
import {
|
import {
|
||||||
@@ -34,13 +32,11 @@ export const ChartDBProvider: React.FC<
|
|||||||
> = ({ children, diagram, readonly }) => {
|
> = ({ children, diagram, readonly }) => {
|
||||||
let db = useStorage();
|
let db = useStorage();
|
||||||
const events = useEventEmitter<ChartDBEvent>();
|
const events = useEventEmitter<ChartDBEvent>();
|
||||||
const navigate = useNavigate();
|
|
||||||
const { setSchemasFilter, schemasFilter } = useLocalConfig();
|
const { setSchemasFilter, schemasFilter } = useLocalConfig();
|
||||||
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
||||||
useRedoUndoStack();
|
useRedoUndoStack();
|
||||||
const [diagramId, setDiagramId] = useState('');
|
const [diagramId, setDiagramId] = useState('');
|
||||||
const [diagramName, setDiagramName] = useState('');
|
const [diagramName, setDiagramName] = useState('');
|
||||||
const { updateConfig } = useConfig();
|
|
||||||
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
||||||
const [diagramUpdatedAt, setDiagramUpdatedAt] = useState<Date>(new Date());
|
const [diagramUpdatedAt, setDiagramUpdatedAt] = useState<Date>(new Date());
|
||||||
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
||||||
@@ -173,34 +169,13 @@ export const ChartDBProvider: React.FC<
|
|||||||
resetRedoStack();
|
resetRedoStack();
|
||||||
resetUndoStack();
|
resetUndoStack();
|
||||||
|
|
||||||
const [config] = await Promise.all([
|
await Promise.all([
|
||||||
db.getConfig(),
|
|
||||||
db.deleteDiagramTables(diagramId),
|
db.deleteDiagramTables(diagramId),
|
||||||
db.deleteDiagramRelationships(diagramId),
|
db.deleteDiagramRelationships(diagramId),
|
||||||
db.deleteDiagram(diagramId),
|
db.deleteDiagram(diagramId),
|
||||||
db.deleteDiagramDependencies(diagramId),
|
db.deleteDiagramDependencies(diagramId),
|
||||||
]);
|
]);
|
||||||
|
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
||||||
if (config?.defaultDiagramId === diagramId) {
|
|
||||||
const diagrams = await db.listDiagrams();
|
|
||||||
|
|
||||||
if (diagrams.length > 0) {
|
|
||||||
const defaultDiagramId = diagrams[0].id;
|
|
||||||
await updateConfig({ defaultDiagramId });
|
|
||||||
navigate(`/diagrams/${defaultDiagramId}`);
|
|
||||||
} else {
|
|
||||||
await updateConfig({ defaultDiagramId: '' });
|
|
||||||
navigate('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
db,
|
|
||||||
diagramId,
|
|
||||||
navigate,
|
|
||||||
resetRedoStack,
|
|
||||||
resetUndoStack,
|
|
||||||
updateConfig,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const updateDiagramUpdatedAt: ChartDBContext['updateDiagramUpdatedAt'] =
|
const updateDiagramUpdatedAt: ChartDBContext['updateDiagramUpdatedAt'] =
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import type { TableSchemaDialogProps } from '@/dialogs/table-schema-dialog/table
|
|||||||
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
||||||
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
||||||
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
||||||
|
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
|
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
||||||
|
|
||||||
export interface DialogContext {
|
export interface DialogContext {
|
||||||
// Create diagram dialog
|
// Create diagram dialog
|
||||||
@@ -48,6 +50,18 @@ export interface DialogContext {
|
|||||||
params: Omit<ExportImageDialogProps, 'dialog'>
|
params: Omit<ExportImageDialogProps, 'dialog'>
|
||||||
) => void;
|
) => void;
|
||||||
closeExportImageDialog: () => void;
|
closeExportImageDialog: () => void;
|
||||||
|
|
||||||
|
// Export diagram dialog
|
||||||
|
openExportDiagramDialog: (
|
||||||
|
params: Omit<ExportDiagramDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
|
closeExportDiagramDialog: () => void;
|
||||||
|
|
||||||
|
// Import diagram dialog
|
||||||
|
openImportDiagramDialog: (
|
||||||
|
params: Omit<ImportDiagramDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
|
closeImportDiagramDialog: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dialogContext = createContext<DialogContext>({
|
export const dialogContext = createContext<DialogContext>({
|
||||||
@@ -69,4 +83,8 @@ export const dialogContext = createContext<DialogContext>({
|
|||||||
closeStarUsDialog: emptyFn,
|
closeStarUsDialog: emptyFn,
|
||||||
openExportImageDialog: emptyFn,
|
openExportImageDialog: emptyFn,
|
||||||
closeExportImageDialog: emptyFn,
|
closeExportImageDialog: emptyFn,
|
||||||
|
openExportDiagramDialog: emptyFn,
|
||||||
|
closeExportDiagramDialog: emptyFn,
|
||||||
|
openImportDiagramDialog: emptyFn,
|
||||||
|
closeImportDiagramDialog: emptyFn,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { emptyFn } from '@/lib/utils';
|
|||||||
import { StarUsDialog } from '@/dialogs/star-us-dialog/star-us-dialog';
|
import { StarUsDialog } from '@/dialogs/star-us-dialog/star-us-dialog';
|
||||||
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
||||||
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
|
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
|
||||||
|
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
|
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
||||||
|
|
||||||
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -86,6 +88,14 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[setOpenTableSchemaDialog]
|
[setOpenTableSchemaDialog]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Export image dialog
|
||||||
|
const [openExportDiagramDialog, setOpenExportDiagramDialog] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
// Import diagram dialog
|
||||||
|
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
// Alert dialog
|
// Alert dialog
|
||||||
const [showAlert, setShowAlert] = useState(false);
|
const [showAlert, setShowAlert] = useState(false);
|
||||||
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
|
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
|
||||||
@@ -126,6 +136,12 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
closeStarUsDialog: () => setOpenStarUsDialog(false),
|
closeStarUsDialog: () => setOpenStarUsDialog(false),
|
||||||
closeExportImageDialog: () => setOpenExportImageDialog(false),
|
closeExportImageDialog: () => setOpenExportImageDialog(false),
|
||||||
openExportImageDialog: openExportImageDialogHandler,
|
openExportImageDialog: openExportImageDialogHandler,
|
||||||
|
openExportDiagramDialog: () => setOpenExportDiagramDialog(true),
|
||||||
|
closeExportDiagramDialog: () =>
|
||||||
|
setOpenExportDiagramDialog(false),
|
||||||
|
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
|
||||||
|
closeImportDiagramDialog: () =>
|
||||||
|
setOpenImportDiagramDialog(false),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -152,6 +168,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
dialog={{ open: openExportImageDialog }}
|
dialog={{ open: openExportImageDialog }}
|
||||||
{...exportImageDialogParams}
|
{...exportImageDialogParams}
|
||||||
/>
|
/>
|
||||||
|
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
|
||||||
|
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
|
||||||
</dialogContext.Provider>
|
</dialogContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import { toJpeg, toPng, toSvg } from 'html-to-image';
|
|||||||
import { useReactFlow } from '@xyflow/react';
|
import { useReactFlow } from '@xyflow/react';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
||||||
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
|
|
||||||
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { hideLoader, showLoader } = useFullScreenLoader();
|
const { hideLoader, showLoader } = useFullScreenLoader();
|
||||||
const { setNodes, getViewport } = useReactFlow();
|
const { setNodes, getViewport } = useReactFlow();
|
||||||
|
const { effectiveTheme } = useTheme();
|
||||||
const { diagramName } = useChartDB();
|
const { diagramName } = useChartDB();
|
||||||
|
|
||||||
const downloadImage = useCallback(
|
const downloadImage = useCallback(
|
||||||
@@ -59,13 +61,101 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const imageCreateFn = imageCreatorMap[type];
|
const imageCreateFn = imageCreatorMap[type];
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const dataUrl = await imageCreateFn(
|
const viewportElement = window.document.querySelector(
|
||||||
window.document.querySelector(
|
'.react-flow__viewport'
|
||||||
'.react-flow__viewport'
|
) as HTMLElement;
|
||||||
) as HTMLElement,
|
|
||||||
{
|
const markerDefs = document.querySelector(
|
||||||
|
'.marker-definitions defs'
|
||||||
|
);
|
||||||
|
|
||||||
|
const tempSvg = document.createElementNS(
|
||||||
|
'http://www.w3.org/2000/svg',
|
||||||
|
'svg'
|
||||||
|
);
|
||||||
|
tempSvg.style.position = 'absolute';
|
||||||
|
tempSvg.style.top = '0';
|
||||||
|
tempSvg.style.left = '0';
|
||||||
|
tempSvg.style.width = '100%';
|
||||||
|
tempSvg.style.height = '100%';
|
||||||
|
tempSvg.style.overflow = 'visible';
|
||||||
|
tempSvg.style.zIndex = '-50';
|
||||||
|
tempSvg.setAttribute(
|
||||||
|
'viewBox',
|
||||||
|
`0 0 ${reactFlowBounds.width} ${reactFlowBounds.height}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const defs = document.createElementNS(
|
||||||
|
'http://www.w3.org/2000/svg',
|
||||||
|
'defs'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (markerDefs) {
|
||||||
|
defs.innerHTML = markerDefs.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = document.createElementNS(
|
||||||
|
'http://www.w3.org/2000/svg',
|
||||||
|
'pattern'
|
||||||
|
);
|
||||||
|
pattern.setAttribute('id', 'background-pattern');
|
||||||
|
pattern.setAttribute('width', String(16 * viewport.zoom));
|
||||||
|
pattern.setAttribute('height', String(16 * viewport.zoom));
|
||||||
|
pattern.setAttribute('patternUnits', 'userSpaceOnUse');
|
||||||
|
pattern.setAttribute(
|
||||||
|
'patternTransform',
|
||||||
|
`translate(${viewport.x % (16 * viewport.zoom)} ${viewport.y % (16 * viewport.zoom)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
const dot = document.createElementNS(
|
||||||
|
'http://www.w3.org/2000/svg',
|
||||||
|
'circle'
|
||||||
|
);
|
||||||
|
|
||||||
|
const dotSize = viewport.zoom * 0.5;
|
||||||
|
dot.setAttribute('cx', String(viewport.zoom));
|
||||||
|
dot.setAttribute('cy', String(viewport.zoom));
|
||||||
|
dot.setAttribute('r', String(dotSize));
|
||||||
|
const dotColor =
|
||||||
|
effectiveTheme === 'light' ? '#92939C' : '#777777';
|
||||||
|
dot.setAttribute('fill', dotColor);
|
||||||
|
|
||||||
|
pattern.appendChild(dot);
|
||||||
|
defs.appendChild(pattern);
|
||||||
|
tempSvg.appendChild(defs);
|
||||||
|
|
||||||
|
const backgroundRect = document.createElementNS(
|
||||||
|
'http://www.w3.org/2000/svg',
|
||||||
|
'rect'
|
||||||
|
);
|
||||||
|
const padding = 2000;
|
||||||
|
backgroundRect.setAttribute('x', String(-viewport.x - padding));
|
||||||
|
backgroundRect.setAttribute('y', String(-viewport.y - padding));
|
||||||
|
backgroundRect.setAttribute(
|
||||||
|
'width',
|
||||||
|
String(reactFlowBounds.width + 2 * padding)
|
||||||
|
);
|
||||||
|
backgroundRect.setAttribute(
|
||||||
|
'height',
|
||||||
|
String(reactFlowBounds.height + 2 * padding)
|
||||||
|
);
|
||||||
|
backgroundRect.setAttribute('fill', 'url(#background-pattern)');
|
||||||
|
tempSvg.appendChild(backgroundRect);
|
||||||
|
|
||||||
|
viewportElement.insertBefore(
|
||||||
|
tempSvg,
|
||||||
|
viewportElement.firstChild
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dataUrl = await imageCreateFn(viewportElement, {
|
||||||
...(type === 'jpeg' || type === 'png'
|
...(type === 'jpeg' || type === 'png'
|
||||||
? { backgroundColor: '#ffffff' }
|
? {
|
||||||
|
backgroundColor:
|
||||||
|
effectiveTheme === 'light'
|
||||||
|
? '#ffffff'
|
||||||
|
: '#141414',
|
||||||
|
}
|
||||||
: {}),
|
: {}),
|
||||||
width: reactFlowBounds.width,
|
width: reactFlowBounds.width,
|
||||||
height: reactFlowBounds.height,
|
height: reactFlowBounds.height,
|
||||||
@@ -76,11 +166,13 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
},
|
},
|
||||||
quality: 1,
|
quality: 1,
|
||||||
pixelRatio: scale,
|
pixelRatio: scale,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
downloadImage(dataUrl, type);
|
downloadImage(dataUrl, type);
|
||||||
hideLoader();
|
} finally {
|
||||||
|
viewportElement.removeChild(tempSvg);
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -90,6 +182,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
imageCreatorMap,
|
imageCreatorMap,
|
||||||
setNodes,
|
setNodes,
|
||||||
showLoader,
|
showLoader,
|
||||||
|
effectiveTheme,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import { useHistory } from '@/hooks/use-history';
|
import { useHistory } from '@/hooks/use-history';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
|
|
||||||
export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -15,6 +16,8 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const { redo, undo } = useHistory();
|
const { redo, undo } = useHistory();
|
||||||
const { openOpenDiagramDialog } = useDialog();
|
const { openOpenDiagramDialog } = useDialog();
|
||||||
const { updateDiagramUpdatedAt } = useChartDB();
|
const { updateDiagramUpdatedAt } = useChartDB();
|
||||||
|
const { toggleSidePanel } = useLayout();
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
|
keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
|
||||||
redo,
|
redo,
|
||||||
@@ -49,6 +52,15 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
},
|
},
|
||||||
[updateDiagramUpdatedAt]
|
[updateDiagramUpdatedAt]
|
||||||
);
|
);
|
||||||
|
useHotkeys(
|
||||||
|
keyboardShortcutsForOS[KeyboardShortcutAction.TOGGLE_SIDE_PANEL]
|
||||||
|
.keyCombination,
|
||||||
|
toggleSidePanel,
|
||||||
|
{
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[toggleSidePanel]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<keyboardShortcutsContext.Provider value={{}}>
|
<keyboardShortcutsContext.Provider value={{}}>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export enum KeyboardShortcutAction {
|
|||||||
UNDO = 'undo',
|
UNDO = 'undo',
|
||||||
OPEN_DIAGRAM = 'open_diagram',
|
OPEN_DIAGRAM = 'open_diagram',
|
||||||
SAVE_DIAGRAM = 'save_diagram',
|
SAVE_DIAGRAM = 'save_diagram',
|
||||||
|
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyboardShortcut {
|
export interface KeyboardShortcut {
|
||||||
@@ -47,6 +48,13 @@ export const keyboardShortcuts: Record<
|
|||||||
keyCombinationMac: 'meta+s',
|
keyCombinationMac: 'meta+s',
|
||||||
keyCombinationWin: 'ctrl+s',
|
keyCombinationWin: 'ctrl+s',
|
||||||
},
|
},
|
||||||
|
[KeyboardShortcutAction.TOGGLE_SIDE_PANEL]: {
|
||||||
|
action: KeyboardShortcutAction.TOGGLE_SIDE_PANEL,
|
||||||
|
keyCombinationLabelMac: '⌘B',
|
||||||
|
keyCombinationLabelWin: 'Ctrl+B',
|
||||||
|
keyCombinationMac: 'meta+b',
|
||||||
|
keyCombinationWin: 'ctrl+b',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface KeyboardShortcutForOS {
|
export interface KeyboardShortcutForOS {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface LayoutContext {
|
|||||||
isSidePanelShowed: boolean;
|
isSidePanelShowed: boolean;
|
||||||
hideSidePanel: () => void;
|
hideSidePanel: () => void;
|
||||||
showSidePanel: () => void;
|
showSidePanel: () => void;
|
||||||
|
toggleSidePanel: () => void;
|
||||||
|
|
||||||
isSelectSchemaOpen: boolean;
|
isSelectSchemaOpen: boolean;
|
||||||
openSelectSchema: () => void;
|
openSelectSchema: () => void;
|
||||||
@@ -47,6 +48,7 @@ export const layoutContext = createContext<LayoutContext>({
|
|||||||
isSidePanelShowed: false,
|
isSidePanelShowed: false,
|
||||||
hideSidePanel: emptyFn,
|
hideSidePanel: emptyFn,
|
||||||
showSidePanel: emptyFn,
|
showSidePanel: emptyFn,
|
||||||
|
toggleSidePanel: emptyFn,
|
||||||
|
|
||||||
isSelectSchemaOpen: false,
|
isSelectSchemaOpen: false,
|
||||||
openSelectSchema: emptyFn,
|
openSelectSchema: emptyFn,
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const showSidePanel: LayoutContext['showSidePanel'] = () =>
|
const showSidePanel: LayoutContext['showSidePanel'] = () =>
|
||||||
setIsSidePanelShowed(true);
|
setIsSidePanelShowed(true);
|
||||||
|
|
||||||
|
const toggleSidePanel: LayoutContext['toggleSidePanel'] = () => {
|
||||||
|
setIsSidePanelShowed((prevIsSidePanelShowed) => !prevIsSidePanelShowed);
|
||||||
|
};
|
||||||
|
|
||||||
const openTableFromSidebar: LayoutContext['openTableFromSidebar'] = (
|
const openTableFromSidebar: LayoutContext['openTableFromSidebar'] = (
|
||||||
tableId
|
tableId
|
||||||
) => {
|
) => {
|
||||||
@@ -77,6 +81,7 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
isSidePanelShowed,
|
isSidePanelShowed,
|
||||||
hideSidePanel,
|
hideSidePanel,
|
||||||
showSidePanel,
|
showSidePanel,
|
||||||
|
toggleSidePanel,
|
||||||
isSelectSchemaOpen,
|
isSelectSchemaOpen,
|
||||||
openSelectSchema,
|
openSelectSchema,
|
||||||
closeSelectSchema,
|
closeSelectSchema,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogInternalContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/dialog/dialog';
|
} from '@/components/dialog/dialog';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
||||||
@@ -139,6 +140,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
setScriptResult(fixedJson);
|
setScriptResult(fixedJson);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
} else {
|
} else {
|
||||||
|
setScriptResult(fixedJson);
|
||||||
setErrorMessage(errorScriptOutputMessage);
|
setErrorMessage(errorScriptOutputMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,188 +159,201 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
|
|
||||||
const renderContent = useCallback(() => {
|
const renderContent = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-1 flex-col gap-6">
|
<DialogInternalContent>
|
||||||
{databaseTypeToEditionMap[databaseType].length > 0 ? (
|
<div className="flex w-full flex-1 flex-col gap-6">
|
||||||
<div className="flex flex-col gap-1 md:flex-row">
|
{databaseTypeToEditionMap[databaseType].length > 0 ? (
|
||||||
<p className="text-sm leading-6 text-muted-foreground">
|
<div className="flex flex-col gap-1 md:flex-row">
|
||||||
{t(
|
<p className="text-sm leading-6 text-muted-foreground">
|
||||||
'new_diagram_dialog.import_database.database_edition'
|
{t(
|
||||||
)}
|
'new_diagram_dialog.import_database.database_edition'
|
||||||
</p>
|
)}
|
||||||
<ToggleGroup
|
</p>
|
||||||
type="single"
|
<ToggleGroup
|
||||||
className="ml-1 flex-wrap gap-2"
|
type="single"
|
||||||
value={
|
className="ml-1 flex-wrap gap-2"
|
||||||
!databaseEdition ? 'regular' : databaseEdition
|
value={
|
||||||
}
|
!databaseEdition
|
||||||
onValueChange={(value) => {
|
? 'regular'
|
||||||
setDatabaseEdition(
|
: databaseEdition
|
||||||
value === 'regular'
|
}
|
||||||
? undefined
|
onValueChange={(value) => {
|
||||||
: (value as DatabaseEdition)
|
setDatabaseEdition(
|
||||||
);
|
value === 'regular'
|
||||||
}}
|
? undefined
|
||||||
>
|
: (value as DatabaseEdition)
|
||||||
<ToggleGroupItem
|
);
|
||||||
value="regular"
|
}}
|
||||||
variant="outline"
|
|
||||||
className="h-6 gap-1 p-0 px-2 shadow-none"
|
|
||||||
>
|
>
|
||||||
<Avatar className="size-4 rounded-none">
|
<ToggleGroupItem
|
||||||
<AvatarImage
|
value="regular"
|
||||||
src={
|
variant="outline"
|
||||||
databaseSecondaryLogoMap[
|
className="h-6 gap-1 p-0 px-2 shadow-none"
|
||||||
databaseType
|
>
|
||||||
]
|
<Avatar className="size-4 rounded-none">
|
||||||
}
|
<AvatarImage
|
||||||
alt="Regular"
|
src={
|
||||||
/>
|
databaseSecondaryLogoMap[
|
||||||
<AvatarFallback>Regular</AvatarFallback>
|
databaseType
|
||||||
</Avatar>
|
]
|
||||||
Regular
|
}
|
||||||
</ToggleGroupItem>
|
alt="Regular"
|
||||||
{databaseTypeToEditionMap[databaseType].map(
|
/>
|
||||||
(edition) => (
|
<AvatarFallback>Regular</AvatarFallback>
|
||||||
<ToggleGroupItem
|
</Avatar>
|
||||||
value={edition}
|
Regular
|
||||||
key={edition}
|
</ToggleGroupItem>
|
||||||
variant="outline"
|
{databaseTypeToEditionMap[databaseType].map(
|
||||||
className="h-6 gap-1 p-0 px-2 shadow-none"
|
(edition) => (
|
||||||
>
|
<ToggleGroupItem
|
||||||
<Avatar className="size-4">
|
value={edition}
|
||||||
<AvatarImage
|
key={edition}
|
||||||
src={
|
variant="outline"
|
||||||
databaseEditionToImageMap[
|
className="h-6 gap-1 p-0 px-2 shadow-none"
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
alt={
|
|
||||||
databaseEditionToLabelMap[
|
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AvatarFallback>
|
|
||||||
{
|
|
||||||
databaseEditionToLabelMap[
|
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{databaseEditionToLabelMap[edition]}
|
|
||||||
</ToggleGroupItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</ToggleGroup>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
|
|
||||||
<div>
|
|
||||||
1. {t('new_diagram_dialog.import_database.step_1')}
|
|
||||||
</div>
|
|
||||||
{databaseType === DatabaseType.SQL_SERVER && (
|
|
||||||
<SSMSInfo />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{databaseTypeToClientsMap[databaseType].length > 0 ? (
|
|
||||||
<Tabs
|
|
||||||
value={
|
|
||||||
!databaseClient ? 'dbclient' : databaseClient
|
|
||||||
}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setDatabaseClient(
|
|
||||||
value === 'dbclient'
|
|
||||||
? undefined
|
|
||||||
: (value as DatabaseClient)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex flex-1">
|
|
||||||
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
|
|
||||||
<TabsTrigger
|
|
||||||
value="dbclient"
|
|
||||||
className="h-6 w-20"
|
|
||||||
>
|
|
||||||
DB Client
|
|
||||||
</TabsTrigger>
|
|
||||||
|
|
||||||
{databaseClients?.map((client) => (
|
|
||||||
<TabsTrigger
|
|
||||||
key={client}
|
|
||||||
value={client}
|
|
||||||
className="h-6 !w-20"
|
|
||||||
>
|
>
|
||||||
{databaseClientToLabelMap[client]}
|
<Avatar className="size-4">
|
||||||
</TabsTrigger>
|
<AvatarImage
|
||||||
)) ?? []}
|
src={
|
||||||
</TabsList>
|
databaseEditionToImageMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
alt={
|
||||||
|
databaseEditionToLabelMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AvatarFallback>
|
||||||
|
{
|
||||||
|
databaseEditionToLabelMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{databaseEditionToLabelMap[edition]}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
|
||||||
|
<div>
|
||||||
|
1.{' '}
|
||||||
|
{t('new_diagram_dialog.import_database.step_1')}
|
||||||
</div>
|
</div>
|
||||||
|
{databaseType === DatabaseType.SQL_SERVER && (
|
||||||
|
<SSMSInfo />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{databaseTypeToClientsMap[databaseType].length > 0 ? (
|
||||||
|
<Tabs
|
||||||
|
value={
|
||||||
|
!databaseClient
|
||||||
|
? 'dbclient'
|
||||||
|
: databaseClient
|
||||||
|
}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setDatabaseClient(
|
||||||
|
value === 'dbclient'
|
||||||
|
? undefined
|
||||||
|
: (value as DatabaseClient)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-1">
|
||||||
|
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
|
||||||
|
<TabsTrigger
|
||||||
|
value="dbclient"
|
||||||
|
className="h-6 w-20"
|
||||||
|
>
|
||||||
|
DB Client
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
{databaseClients?.map((client) => (
|
||||||
|
<TabsTrigger
|
||||||
|
key={client}
|
||||||
|
value={client}
|
||||||
|
className="h-6 !w-20"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
databaseClientToLabelMap[
|
||||||
|
client
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</TabsTrigger>
|
||||||
|
)) ?? []}
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
<CodeSnippet
|
||||||
|
className="h-40 w-full"
|
||||||
|
loading={!importMetadataScripts}
|
||||||
|
code={
|
||||||
|
importMetadataScripts?.[databaseType]?.(
|
||||||
|
{
|
||||||
|
databaseEdition,
|
||||||
|
databaseClient,
|
||||||
|
}
|
||||||
|
) ?? ''
|
||||||
|
}
|
||||||
|
language={databaseClient ? 'shell' : 'sql'}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
) : (
|
||||||
<CodeSnippet
|
<CodeSnippet
|
||||||
className="h-40 w-full"
|
className="h-40 w-full flex-auto"
|
||||||
loading={!importMetadataScripts}
|
loading={!importMetadataScripts}
|
||||||
code={
|
code={
|
||||||
importMetadataScripts?.[databaseType]?.({
|
importMetadataScripts?.[databaseType]?.({
|
||||||
databaseEdition,
|
databaseEdition,
|
||||||
databaseClient,
|
|
||||||
}) ?? ''
|
}) ?? ''
|
||||||
}
|
}
|
||||||
language={databaseClient ? 'shell' : 'sql'}
|
language="sql"
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
|
||||||
) : (
|
|
||||||
<CodeSnippet
|
|
||||||
className="h-40 w-full flex-auto"
|
|
||||||
loading={!importMetadataScripts}
|
|
||||||
code={
|
|
||||||
importMetadataScripts?.[databaseType]?.({
|
|
||||||
databaseEdition,
|
|
||||||
}) ?? ''
|
|
||||||
}
|
|
||||||
language="sql"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex h-48 flex-col gap-1">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
2. {t('new_diagram_dialog.import_database.step_2')}
|
|
||||||
</p>
|
|
||||||
<Textarea
|
|
||||||
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
|
|
||||||
placeholder={t(
|
|
||||||
'new_diagram_dialog.import_database.script_results_placeholder'
|
|
||||||
)}
|
)}
|
||||||
value={scriptResult}
|
</div>
|
||||||
onChange={handleInputChange}
|
<div className="flex h-48 flex-col gap-1">
|
||||||
/>
|
<p className="text-sm text-muted-foreground">
|
||||||
{showCheckJsonButton || errorMessage ? (
|
2. {t('new_diagram_dialog.import_database.step_2')}
|
||||||
<div className="mt-2 flex items-center gap-2">
|
</p>
|
||||||
{showCheckJsonButton ? (
|
<Textarea
|
||||||
<Button
|
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
|
||||||
type="button"
|
placeholder={t(
|
||||||
variant="outline"
|
'new_diagram_dialog.import_database.script_results_placeholder'
|
||||||
size="sm"
|
|
||||||
onClick={handleCheckJson}
|
|
||||||
disabled={isCheckingJson}
|
|
||||||
>
|
|
||||||
{isCheckingJson ? (
|
|
||||||
<Spinner size="small" />
|
|
||||||
) : (
|
|
||||||
t(
|
|
||||||
'new_diagram_dialog.import_database.check_script_result'
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-red-700">
|
|
||||||
{errorMessage}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
value={scriptResult}
|
||||||
) : null}
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
{showCheckJsonButton || errorMessage ? (
|
||||||
|
<div className="mt-2 flex items-center gap-2">
|
||||||
|
{showCheckJsonButton ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleCheckJson}
|
||||||
|
disabled={isCheckingJson}
|
||||||
|
>
|
||||||
|
{isCheckingJson ? (
|
||||||
|
<Spinner size="small" />
|
||||||
|
) : (
|
||||||
|
t(
|
||||||
|
'new_diagram_dialog.import_database.check_script_result'
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-red-700">
|
||||||
|
{errorMessage}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogInternalContent>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
databaseEdition,
|
databaseEdition,
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]"
|
className="flex max-h-screen w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]"
|
||||||
showClose={hasExistingDiagram}
|
showClose={hasExistingDiagram}
|
||||||
>
|
>
|
||||||
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogInternalContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/dialog/dialog';
|
} from '@/components/dialog/dialog';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SelectDatabaseContent } from './select-database-content';
|
import { SelectDatabaseContent } from './select-database-content';
|
||||||
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
|
|
||||||
export interface SelectDatabaseProps {
|
export interface SelectDatabaseProps {
|
||||||
onContinue: () => void;
|
onContinue: () => void;
|
||||||
@@ -27,6 +29,7 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
|||||||
createNewDiagram,
|
createNewDiagram,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { openImportDiagramDialog } = useDialog();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -38,11 +41,13 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
|||||||
{t('new_diagram_dialog.database_selection.description')}
|
{t('new_diagram_dialog.database_selection.description')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<SelectDatabaseContent
|
<DialogInternalContent>
|
||||||
databaseType={databaseType}
|
<SelectDatabaseContent
|
||||||
onContinue={onContinue}
|
databaseType={databaseType}
|
||||||
setDatabaseType={setDatabaseType}
|
onContinue={onContinue}
|
||||||
/>
|
setDatabaseType={setDatabaseType}
|
||||||
|
/>
|
||||||
|
</DialogInternalContent>
|
||||||
<DialogFooter className="mt-4 flex !justify-between gap-2">
|
<DialogFooter className="mt-4 flex !justify-between gap-2">
|
||||||
{hasExistingDiagram ? (
|
{hasExistingDiagram ? (
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
@@ -51,7 +56,13 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
) : (
|
) : (
|
||||||
<div></div>
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={openImportDiagramDialog}
|
||||||
|
>
|
||||||
|
{t('new_diagram_dialog.import_from_file')}
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2">
|
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
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,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogInternalContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/dialog/dialog';
|
} from '@/components/dialog/dialog';
|
||||||
import { Label } from '@/components/label/label';
|
import { Label } from '@/components/label/label';
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import { Annoyed, Sparkles } from 'lucide-react';
|
import { Annoyed, Sparkles } from 'lucide-react';
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
|
|
||||||
@@ -37,28 +38,47 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [script, setScript] = React.useState<string>();
|
const [script, setScript] = React.useState<string>();
|
||||||
const [error, setError] = React.useState<boolean>(false);
|
const [error, setError] = React.useState<boolean>(false);
|
||||||
|
const [isScriptLoading, setIsScriptLoading] =
|
||||||
|
React.useState<boolean>(false);
|
||||||
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
const exportSQLScript = useCallback(async () => {
|
const exportSQLScript = useCallback(async () => {
|
||||||
if (targetDatabaseType === DatabaseType.GENERIC) {
|
if (targetDatabaseType === DatabaseType.GENERIC) {
|
||||||
return Promise.resolve(exportBaseSQL(currentDiagram));
|
return Promise.resolve(exportBaseSQL(currentDiagram));
|
||||||
} else {
|
} else {
|
||||||
return exportSQL(currentDiagram, targetDatabaseType);
|
return exportSQL(currentDiagram, targetDatabaseType, {
|
||||||
|
stream: true,
|
||||||
|
onResultStream: (text) =>
|
||||||
|
setScript((prev) => (prev ? prev + text : text)),
|
||||||
|
signal: abortControllerRef.current?.signal,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [targetDatabaseType, currentDiagram]);
|
}, [targetDatabaseType, currentDiagram]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialog.open) return;
|
if (!dialog.open) {
|
||||||
|
abortControllerRef.current?.abort();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
abortControllerRef.current = new AbortController();
|
||||||
setScript(undefined);
|
setScript(undefined);
|
||||||
setError(false);
|
setError(false);
|
||||||
const fetchScript = async () => {
|
const fetchScript = async () => {
|
||||||
try {
|
try {
|
||||||
|
setIsScriptLoading(true);
|
||||||
const script = await exportSQLScript();
|
const script = await exportSQLScript();
|
||||||
setScript(script);
|
setScript(script);
|
||||||
|
setIsScriptLoading(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(true);
|
setError(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchScript();
|
fetchScript();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
abortControllerRef.current?.abort();
|
||||||
|
};
|
||||||
}, [dialog.open, setScript, exportSQLScript, setError]);
|
}, [dialog.open, setScript, exportSQLScript, setError]);
|
||||||
|
|
||||||
const renderError = useCallback(
|
const renderError = useCallback(
|
||||||
@@ -132,7 +152,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex max-h-[80vh] flex-col overflow-y-auto xl:min-w-[75vw]"
|
className="flex max-h-screen flex-col overflow-y-auto xl:min-w-[75vw]"
|
||||||
showClose
|
showClose
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@@ -148,18 +168,24 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
})}
|
})}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex flex-1 items-center justify-center">
|
<DialogInternalContent>
|
||||||
{error ? (
|
<div className="flex flex-1 items-center justify-center">
|
||||||
renderError()
|
{error ? (
|
||||||
) : script === undefined ? (
|
renderError()
|
||||||
renderLoader()
|
) : script === undefined ? (
|
||||||
) : script.length === 0 ? (
|
renderLoader()
|
||||||
renderError()
|
) : script.length === 0 ? (
|
||||||
) : (
|
renderError()
|
||||||
<CodeSnippet className="h-96 w-full" code={script!} />
|
) : (
|
||||||
)}
|
<CodeSnippet
|
||||||
</div>
|
className="h-96 w-full"
|
||||||
|
code={script!}
|
||||||
|
autoScroll={true}
|
||||||
|
isComplete={!isScriptLoading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogInternalContent>
|
||||||
<DialogFooter className="flex !justify-between gap-2">
|
<DialogFooter className="flex !justify-between gap-2">
|
||||||
<div />
|
<div />
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex w-[90vw] flex-col overflow-y-auto md:overflow-visible xl:min-w-[45vw]"
|
className="flex max-h-screen w-[90vw] flex-col overflow-y-auto md:overflow-visible xl:min-w-[45vw]"
|
||||||
showClose
|
showClose
|
||||||
>
|
>
|
||||||
<ImportDatabase
|
<ImportDatabase
|
||||||
|
|||||||
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,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogInternalContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/dialog/dialog';
|
} from '@/components/dialog/dialog';
|
||||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -74,7 +74,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex w-[90vw] flex-col overflow-y-auto md:w-screen xl:min-w-[55vw]"
|
className="flex h-[30rem] max-h-screen w-[90vw] flex-col overflow-y-auto md:w-screen xl:min-w-[55vw]"
|
||||||
showClose
|
showClose
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@@ -83,9 +83,9 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
|||||||
{t('open_diagram_dialog.description')}
|
{t('open_diagram_dialog.description')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex flex-1 items-center justify-center">
|
<DialogInternalContent>
|
||||||
<ScrollArea className="h-80 w-full">
|
<div className="flex flex-1 items-center justify-center">
|
||||||
<Table className="h-fit">
|
<Table>
|
||||||
<TableHeader className="sticky top-0 bg-background">
|
<TableHeader className="sticky top-0 bg-background">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead />
|
<TableHead />
|
||||||
@@ -155,8 +155,8 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</ScrollArea>
|
</div>
|
||||||
</div>
|
</DialogInternalContent>
|
||||||
|
|
||||||
<DialogFooter className="flex !justify-between gap-2">
|
<DialogFooter className="flex !justify-between gap-2">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
|
|||||||
128
src/globals.css
@@ -3,69 +3,73 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 222.2 84% 4.9%;
|
--foreground: 222.2 84% 4.9%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 222.2 84% 4.9%;
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 222.2 84% 4.9%;
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
--primary: 222.2 47.4% 11.2%;
|
--primary: 222.2 47.4% 11.2%;
|
||||||
--primary-foreground: 210 40% 98%;
|
--primary-foreground: 210 40% 98%;
|
||||||
--secondary: 210 40% 96.1%;
|
--secondary: 210 40% 96.1%;
|
||||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
--muted: 210 40% 96.1%;
|
--muted: 210 40% 96.1%;
|
||||||
--muted-foreground: 215.4 16.3% 46.9%;
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
--accent: 210 40% 96.1%;
|
--accent: 210 40% 96.1%;
|
||||||
--accent-foreground: 222.2 47.4% 11.2%;
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 214.3 31.8% 91.4%;
|
--border: 214.3 31.8% 91.4%;
|
||||||
--input: 214.3 31.8% 91.4%;
|
--input: 214.3 31.8% 91.4%;
|
||||||
--ring: 222.2 84% 4.9%;
|
--ring: 222.2 84% 4.9%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--chart-1: 12 76% 61%;
|
--chart-1: 12 76% 61%;
|
||||||
--chart-2: 173 58% 39%;
|
--chart-2: 173 58% 39%;
|
||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
--subtitle: 215.3 19.3% 34.5%;
|
--subtitle: 215.3 19.3% 34.5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 222.2 84% 4.9%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 40% 98%;
|
||||||
--card: 222.2 84% 4.9%;
|
--card: 222.2 84% 4.9%;
|
||||||
--card-foreground: 210 40% 98%;
|
--card-foreground: 210 40% 98%;
|
||||||
--popover: 222.2 84% 4.9%;
|
--popover: 222.2 84% 4.9%;
|
||||||
--popover-foreground: 210 40% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
--primary: 210 40% 98%;
|
--primary: 210 40% 98%;
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary-foreground: 210 40% 98%;
|
||||||
--muted: 217.2 32.6% 17.5%;
|
--muted: 217.2 32.6% 17.5%;
|
||||||
--muted-foreground: 215 20.2% 65.1%;
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
--accent: 217.2 32.6% 17.5%;
|
--accent: 217.2 32.6% 17.5%;
|
||||||
--accent-foreground: 210 40% 98%;
|
--accent-foreground: 210 40% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 217.2 32.6% 17.5%;
|
--border: 217.2 32.6% 17.5%;
|
||||||
--input: 217.2 32.6% 17.5%;
|
--input: 217.2 32.6% 17.5%;
|
||||||
--ring: 212.7 26.8% 83.9%;
|
--ring: 212.7 26.8% 83.9%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
--chart-3: 30 80% 55%;
|
--chart-3: 30 80% 55%;
|
||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
--subtitle: 212.7 26.8% 83.9%;
|
--subtitle: 212.7 26.8% 83.9%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.text-editable {
|
||||||
|
@apply dark:group-hover:bg-slate-900 group-hover:bg-slate-100 group-hover:ring-[0.5px] rounded-md cursor-pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const HelmetData: React.FC = () => (
|
|||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="og:image"
|
property="og:image"
|
||||||
content="https://app.chartdb.io/ChartDB.png"
|
content="https://app.chartdb.io/chartdb.png"
|
||||||
/>
|
/>
|
||||||
<meta property="og:url" content="https://app.chartdb.io" />
|
<meta property="og:url" content="https://app.chartdb.io" />
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
@@ -32,7 +32,7 @@ export const HelmetData: React.FC = () => (
|
|||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="twitter:image"
|
name="twitter:image"
|
||||||
content="https://github.com/chartdb/chartdb/raw/main/public/ChartDB.png"
|
content="https://github.com/chartdb/chartdb/raw/main/public/chartdb.png"
|
||||||
/>
|
/>
|
||||||
<title>ChartDB - Database schema diagrams visualizer</title>
|
<title>ChartDB - Database schema diagrams visualizer</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|||||||
@@ -1,12 +1,48 @@
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import type { LanguageMetadata } from './types';
|
||||||
import { en, enMetadata } from './locales/en';
|
import { en, enMetadata } from './locales/en';
|
||||||
import { es } from './locales/es';
|
import { es, esMetadata } from './locales/es';
|
||||||
import { fr } from './locales/fr';
|
import { fr, frMetadata } from './locales/fr';
|
||||||
import { de } from './locales/de';
|
import { de, deMetadata } from './locales/de';
|
||||||
import { hi } from './locales/hi';
|
import { hi, hiMetadata } from './locales/hi';
|
||||||
import { ja } from './locales/ja';
|
import { ja, jaMetadata } from './locales/ja';
|
||||||
import { pt_BR } from './locales/pt_BR';
|
import { ko_KR, ko_KRMetadata } from './locales/ko_KR';
|
||||||
|
import { pt_BR, pt_BRMetadata } from './locales/pt_BR';
|
||||||
|
import { uk, ukMetadata } from './locales/uk';
|
||||||
|
import { ru, ruMetadata } from './locales/ru';
|
||||||
|
import { zh_CN, zh_CNMetadata } from './locales/zh_CN';
|
||||||
|
import { zh_TW, zh_TWMetadata } from './locales/zh_TW';
|
||||||
|
import { ne, neMetadata } from './locales/ne';
|
||||||
|
import { mr, mrMetadata } from './locales/mr';
|
||||||
|
import { tr, trMetadata } from './locales/tr';
|
||||||
|
import { id_ID, id_IDMetadata } from './locales/id_ID';
|
||||||
|
import { te, teMetadata } from './locales/te';
|
||||||
|
import { gu, guMetadata } from './locales/gu';
|
||||||
|
import { vi, viMetadata } from './locales/vi';
|
||||||
|
|
||||||
|
export const languages: LanguageMetadata[] = [
|
||||||
|
enMetadata,
|
||||||
|
esMetadata,
|
||||||
|
frMetadata,
|
||||||
|
deMetadata,
|
||||||
|
hiMetadata,
|
||||||
|
jaMetadata,
|
||||||
|
ko_KRMetadata,
|
||||||
|
pt_BRMetadata,
|
||||||
|
ukMetadata,
|
||||||
|
ruMetadata,
|
||||||
|
zh_CNMetadata,
|
||||||
|
zh_TWMetadata,
|
||||||
|
neMetadata,
|
||||||
|
mrMetadata,
|
||||||
|
trMetadata,
|
||||||
|
id_IDMetadata,
|
||||||
|
teMetadata,
|
||||||
|
guMetadata,
|
||||||
|
viMetadata,
|
||||||
|
];
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
en,
|
en,
|
||||||
@@ -15,17 +51,30 @@ const resources = {
|
|||||||
de,
|
de,
|
||||||
hi,
|
hi,
|
||||||
ja,
|
ja,
|
||||||
|
ko_KR,
|
||||||
pt_BR,
|
pt_BR,
|
||||||
|
uk,
|
||||||
|
ru,
|
||||||
|
zh_CN,
|
||||||
|
zh_TW,
|
||||||
|
ne,
|
||||||
|
mr,
|
||||||
|
tr,
|
||||||
|
id_ID,
|
||||||
|
te,
|
||||||
|
gu,
|
||||||
|
vi,
|
||||||
};
|
};
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(LanguageDetector)
|
||||||
resources,
|
.use(initReactI18next)
|
||||||
lng: enMetadata.code,
|
.init({
|
||||||
interpolation: {
|
resources,
|
||||||
escapeValue: false,
|
interpolation: {
|
||||||
},
|
escapeValue: false,
|
||||||
fallbackLng: enMetadata.code,
|
},
|
||||||
debug: false,
|
fallbackLng: enMetadata.code,
|
||||||
});
|
debug: false,
|
||||||
|
});
|
||||||
|
|
||||||
export { i18n };
|
export { i18n };
|
||||||
|
|||||||
@@ -28,10 +28,15 @@ export const de: LanguageTranslation = {
|
|||||||
show_cardinality: 'Kardinalität anzeigen',
|
show_cardinality: 'Kardinalität anzeigen',
|
||||||
zoom_on_scroll: 'Zoom beim Scrollen',
|
zoom_on_scroll: 'Zoom beim Scrollen',
|
||||||
theme: 'Stil',
|
theme: 'Stil',
|
||||||
change_language: 'Sprache',
|
|
||||||
show_dependencies: 'Abhängigkeiten anzeigen',
|
show_dependencies: 'Abhängigkeiten anzeigen',
|
||||||
hide_dependencies: 'Abhängigkeiten ausblenden',
|
hide_dependencies: 'Abhängigkeiten ausblenden',
|
||||||
},
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
share: {
|
||||||
|
share: 'Share',
|
||||||
|
export_diagram: 'Export Diagram',
|
||||||
|
import_diagram: 'Import Diagram',
|
||||||
|
},
|
||||||
help: {
|
help: {
|
||||||
help: 'Hilfe',
|
help: 'Hilfe',
|
||||||
visit_website: 'ChartDB Webseite',
|
visit_website: 'ChartDB Webseite',
|
||||||
@@ -139,6 +144,7 @@ export const de: LanguageTranslation = {
|
|||||||
change_schema: 'Schema ändern',
|
change_schema: 'Schema ändern',
|
||||||
add_field: 'Feld hinzufügen',
|
add_field: 'Feld hinzufügen',
|
||||||
add_index: 'Index hinzufügen',
|
add_index: 'Index hinzufügen',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Tabelle löschen',
|
delete_table: 'Tabelle löschen',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -226,6 +232,8 @@ export const de: LanguageTranslation = {
|
|||||||
|
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
back: 'Zurück',
|
back: 'Zurück',
|
||||||
|
// TODO: Translate
|
||||||
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'Leeres Diagramm',
|
empty_diagram: 'Leeres Diagramm',
|
||||||
continue: 'Weiter',
|
continue: 'Weiter',
|
||||||
import: 'Importieren',
|
import: 'Importieren',
|
||||||
@@ -329,7 +337,31 @@ export const de: LanguageTranslation = {
|
|||||||
close: 'Nicht jetzt',
|
close: 'Nicht jetzt',
|
||||||
confirm: 'Natürlich!',
|
confirm: 'Natürlich!',
|
||||||
},
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
export_diagram_dialog: {
|
||||||
|
title: 'Export Diagram',
|
||||||
|
description: 'Choose the format for export:',
|
||||||
|
format_json: 'JSON',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
export: 'Export',
|
||||||
|
error: {
|
||||||
|
title: 'Error exporting diagram',
|
||||||
|
description:
|
||||||
|
'Something went wrong. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
import_diagram_dialog: {
|
||||||
|
title: 'Import Diagram',
|
||||||
|
description: 'Paste the diagram JSON below:',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
import: 'Import',
|
||||||
|
error: {
|
||||||
|
title: 'Error importing diagram',
|
||||||
|
description:
|
||||||
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
one_to_one: 'Ein zu Eins (1:1)',
|
one_to_one: 'Ein zu Eins (1:1)',
|
||||||
one_to_many: 'Ein zu Viele (1:n)',
|
one_to_many: 'Ein zu Viele (1:n)',
|
||||||
@@ -344,12 +376,25 @@ export const de: LanguageTranslation = {
|
|||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Tabelle bearbeiten',
|
edit_table: 'Tabelle bearbeiten',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Tabelle löschen',
|
delete_table: 'Tabelle löschen',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Add translations
|
||||||
|
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
||||||
|
|
||||||
|
tool_tips: {
|
||||||
|
double_click_to_edit: 'Doppelklicken zum Bearbeiten',
|
||||||
|
},
|
||||||
|
|
||||||
|
language_select: {
|
||||||
|
change_language: 'Sprache',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deMetadata: LanguageMetadata = {
|
export const deMetadata: LanguageMetadata = {
|
||||||
name: 'Deutsch',
|
name: 'German',
|
||||||
|
nativeName: 'Deutsch',
|
||||||
code: 'de',
|
code: 'de',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,10 +28,14 @@ export const en = {
|
|||||||
show_cardinality: 'Show Cardinality',
|
show_cardinality: 'Show Cardinality',
|
||||||
zoom_on_scroll: 'Zoom on Scroll',
|
zoom_on_scroll: 'Zoom on Scroll',
|
||||||
theme: 'Theme',
|
theme: 'Theme',
|
||||||
change_language: 'Language',
|
|
||||||
show_dependencies: 'Show Dependencies',
|
show_dependencies: 'Show Dependencies',
|
||||||
hide_dependencies: 'Hide Dependencies',
|
hide_dependencies: 'Hide Dependencies',
|
||||||
},
|
},
|
||||||
|
share: {
|
||||||
|
share: 'Share',
|
||||||
|
export_diagram: 'Export Diagram',
|
||||||
|
import_diagram: 'Import Diagram',
|
||||||
|
},
|
||||||
help: {
|
help: {
|
||||||
help: 'Help',
|
help: 'Help',
|
||||||
visit_website: 'Visit ChartDB',
|
visit_website: 'Visit ChartDB',
|
||||||
@@ -139,6 +143,7 @@ export const en = {
|
|||||||
change_schema: 'Change Schema',
|
change_schema: 'Change Schema',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
add_index: 'Add Index',
|
add_index: 'Add Index',
|
||||||
|
duplicate_table: 'Duplicate Table',
|
||||||
delete_table: 'Delete Table',
|
delete_table: 'Delete Table',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -224,6 +229,7 @@ export const en = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
import_from_file: 'Import from File',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
empty_diagram: 'Empty diagram',
|
empty_diagram: 'Empty diagram',
|
||||||
continue: 'Continue',
|
continue: 'Continue',
|
||||||
@@ -328,7 +334,30 @@ export const en = {
|
|||||||
close: 'Not now',
|
close: 'Not now',
|
||||||
confirm: 'Of course!',
|
confirm: 'Of course!',
|
||||||
},
|
},
|
||||||
|
export_diagram_dialog: {
|
||||||
|
title: 'Export Diagram',
|
||||||
|
description: 'Choose the format for export:',
|
||||||
|
format_json: 'JSON',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
export: 'Export',
|
||||||
|
error: {
|
||||||
|
title: 'Error exporting diagram',
|
||||||
|
description:
|
||||||
|
'Something went wrong. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
import_diagram_dialog: {
|
||||||
|
title: 'Import Diagram',
|
||||||
|
description: 'Paste the diagram JSON below:',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
import: 'Import',
|
||||||
|
error: {
|
||||||
|
title: 'Error importing diagram',
|
||||||
|
description:
|
||||||
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
one_to_one: 'One to One',
|
one_to_one: 'One to One',
|
||||||
one_to_many: 'One to Many',
|
one_to_many: 'One to Many',
|
||||||
@@ -343,12 +372,24 @@ export const en = {
|
|||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Edit Table',
|
edit_table: 'Edit Table',
|
||||||
|
duplicate_table: 'Duplicate Table',
|
||||||
delete_table: 'Delete Table',
|
delete_table: 'Delete Table',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
||||||
|
|
||||||
|
tool_tips: {
|
||||||
|
double_click_to_edit: 'Double-click to edit',
|
||||||
|
},
|
||||||
|
|
||||||
|
language_select: {
|
||||||
|
change_language: 'Language',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enMetadata: LanguageMetadata = {
|
export const enMetadata: LanguageMetadata = {
|
||||||
name: 'English',
|
name: 'English',
|
||||||
|
nativeName: 'English',
|
||||||
code: 'en',
|
code: 'en',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,10 +28,14 @@ export const es: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Ocultar Barra Lateral',
|
hide_sidebar: 'Ocultar Barra Lateral',
|
||||||
zoom_on_scroll: 'Zoom al Desplazarse',
|
zoom_on_scroll: 'Zoom al Desplazarse',
|
||||||
theme: 'Tema',
|
theme: 'Tema',
|
||||||
change_language: 'Idioma',
|
show_dependencies: 'Mostrar dependencias',
|
||||||
// TODO: Translate
|
hide_dependencies: 'Ocultar dependencias',
|
||||||
show_dependencies: 'Show Dependencies',
|
},
|
||||||
hide_dependencies: 'Hide Dependencies',
|
// TODO: Translate
|
||||||
|
share: {
|
||||||
|
share: 'Share',
|
||||||
|
export_diagram: 'Export Diagram',
|
||||||
|
import_diagram: 'Import Diagram',
|
||||||
},
|
},
|
||||||
help: {
|
help: {
|
||||||
help: 'Ayuda',
|
help: 'Ayuda',
|
||||||
@@ -80,20 +84,19 @@ export const es: LanguageTranslation = {
|
|||||||
saved: 'Guardado',
|
saved: 'Guardado',
|
||||||
diagrams: 'Diagramas',
|
diagrams: 'Diagramas',
|
||||||
loading_diagram: 'Cargando diagrama...',
|
loading_diagram: 'Cargando diagrama...',
|
||||||
deselect_all: 'Deselect All', // TODO: Translate
|
deselect_all: 'Deseleccionar todo',
|
||||||
select_all: 'Select All', // TODO: Translate
|
select_all: 'Seleccionar todo',
|
||||||
clear: 'Clear', // TODO: Translate
|
clear: 'Limpiar',
|
||||||
show_more: 'Show More', // TODO: Translate
|
show_more: 'Mostrar más',
|
||||||
show_less: 'Show Less', // TODO: Translate
|
show_less: 'Mostrar menos',
|
||||||
// TODO: Translate
|
|
||||||
copy_to_clipboard: 'Copy to Clipboard',
|
copy_to_clipboard: 'Copy to Clipboard',
|
||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schema:', // TODO: Translate
|
schema: 'Esquema:',
|
||||||
filter_by_schema: 'Filter by schema', // TODO: Translate
|
filter_by_schema: 'Filtrar por esquema',
|
||||||
search_schema: 'Search schema...', // TODO: Translate
|
search_schema: 'Buscar esquema...',
|
||||||
no_schemas_found: 'No schemas found.', // TODO: Translate
|
no_schemas_found: 'No se encontraron esquemas.',
|
||||||
view_all_options: 'Ver todas las opciones...',
|
view_all_options: 'Ver todas las opciones...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tablas',
|
tables: 'Tablas',
|
||||||
@@ -113,7 +116,7 @@ export const es: LanguageTranslation = {
|
|||||||
index_select_fields: 'Seleccionar campos',
|
index_select_fields: 'Seleccionar campos',
|
||||||
field_name: 'Nombre',
|
field_name: 'Nombre',
|
||||||
field_type: 'Tipo',
|
field_type: 'Tipo',
|
||||||
no_types_found: 'No types found', // TODO: Translate
|
no_types_found: 'No se encontraron tipos',
|
||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Atributos del Campo',
|
title: 'Atributos del Campo',
|
||||||
unique: 'Único',
|
unique: 'Único',
|
||||||
@@ -132,6 +135,7 @@ export const es: LanguageTranslation = {
|
|||||||
change_schema: 'Cambiar Esquema',
|
change_schema: 'Cambiar Esquema',
|
||||||
add_field: 'Agregar Campo',
|
add_field: 'Agregar Campo',
|
||||||
add_index: 'Agregar Índice',
|
add_index: 'Agregar Índice',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Eliminar Tabla',
|
delete_table: 'Eliminar Tabla',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -160,23 +164,22 @@ export const es: LanguageTranslation = {
|
|||||||
description: 'Crea una relación para conectar tablas',
|
description: 'Crea una relación para conectar tablas',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
|
||||||
dependencies_section: {
|
dependencies_section: {
|
||||||
dependencies: 'Dependencies',
|
dependencies: 'Dependencias',
|
||||||
filter: 'Filter',
|
filter: 'Filtro',
|
||||||
collapse: 'Collapse All',
|
collapse: 'Colapsar todo',
|
||||||
dependency: {
|
dependency: {
|
||||||
table: 'Table',
|
table: 'Tabla',
|
||||||
dependent_table: 'Dependent View',
|
dependent_table: 'Vista dependiente',
|
||||||
delete_dependency: 'Delete',
|
delete_dependency: 'Eliminar',
|
||||||
dependency_actions: {
|
dependency_actions: {
|
||||||
title: 'Actions',
|
title: 'Acciones',
|
||||||
delete_dependency: 'Delete',
|
delete_dependency: 'Eliminar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'No dependencies',
|
title: 'Sin dependencias',
|
||||||
description: 'Create a view to get started',
|
description: 'Crea una vista para comenzar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -189,8 +192,7 @@ export const es: LanguageTranslation = {
|
|||||||
undo: 'Deshacer',
|
undo: 'Deshacer',
|
||||||
redo: 'Rehacer',
|
redo: 'Rehacer',
|
||||||
reorder_diagram: 'Reordenar Diagrama',
|
reorder_diagram: 'Reordenar Diagrama',
|
||||||
// TODO: Translate
|
highlight_overlapping_tables: 'Resaltar tablas superpuestas',
|
||||||
highlight_overlapping_tables: 'Highlight Overlapping Tables',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -214,20 +216,20 @@ export const es: LanguageTranslation = {
|
|||||||
step_1: 'Ve a Herramientas > Opciones > Resultados de Consulta > SQL Server.',
|
step_1: 'Ve a Herramientas > Opciones > Resultados de Consulta > SQL Server.',
|
||||||
step_2: 'Si estás usando "Resultados en Cuadrícula", cambia el Máximo de Caracteres Recuperados para Datos No XML (configúralo en 9999999).',
|
step_2: 'Si estás usando "Resultados en Cuadrícula", cambia el Máximo de Caracteres Recuperados para Datos No XML (configúralo en 9999999).',
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
instructions_link: '¿Necesitas ayuda? mira cómo',
|
||||||
instructions_link: 'Need help? Watch how',
|
check_script_result: 'Revisa el resultado del script',
|
||||||
check_script_result: 'Check Script Result',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
back: 'Atrás',
|
back: 'Atrás',
|
||||||
|
// TODO: Translate
|
||||||
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'Diagrama vacío',
|
empty_diagram: 'Diagrama vacío',
|
||||||
continue: 'Continuar',
|
continue: 'Continuar',
|
||||||
import: 'Importar',
|
import: 'Importar',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
// TODO: Translate
|
|
||||||
title: 'Abrir Diagrama',
|
title: 'Abrir Diagrama',
|
||||||
description:
|
description:
|
||||||
'Selecciona un diagrama para abrir de la lista a continuación.',
|
'Selecciona un diagrama para abrir de la lista a continuación.',
|
||||||
@@ -293,16 +295,15 @@ export const es: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
|
||||||
export_image_dialog: {
|
export_image_dialog: {
|
||||||
title: 'Export Image',
|
title: 'Exportar imagen',
|
||||||
description: 'Choose the scale factor for export:',
|
description: 'Escoge el factor de escalamiento para exportar:',
|
||||||
scale_1x: '1x Regular',
|
scale_1x: '1x regular',
|
||||||
scale_2x: '2x (Recommended)',
|
scale_2x: '2x (recomendado)',
|
||||||
scale_3x: '3x',
|
scale_3x: '3x',
|
||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancelar',
|
||||||
export: 'Export',
|
export: 'Exportar',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -336,7 +337,31 @@ export const es: LanguageTranslation = {
|
|||||||
change_schema: 'Cambiar',
|
change_schema: 'Cambiar',
|
||||||
none: 'nada',
|
none: 'nada',
|
||||||
},
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
export_diagram_dialog: {
|
||||||
|
title: 'Export Diagram',
|
||||||
|
description: 'Choose the format for export:',
|
||||||
|
format_json: 'JSON',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
export: 'Export',
|
||||||
|
error: {
|
||||||
|
title: 'Error exporting diagram',
|
||||||
|
description:
|
||||||
|
'Something went wrong. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
import_diagram_dialog: {
|
||||||
|
title: 'Import Diagram',
|
||||||
|
description: 'Paste the diagram JSON below:',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
import: 'Import',
|
||||||
|
error: {
|
||||||
|
title: 'Error importing diagram',
|
||||||
|
description:
|
||||||
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
one_to_one: 'Uno a Uno',
|
one_to_one: 'Uno a Uno',
|
||||||
one_to_many: 'Uno a Muchos',
|
one_to_many: 'Uno a Muchos',
|
||||||
@@ -351,12 +376,25 @@ export const es: LanguageTranslation = {
|
|||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Editar Tabla',
|
edit_table: 'Editar Tabla',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Eliminar Tabla',
|
delete_table: 'Eliminar Tabla',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Add translations
|
||||||
|
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
||||||
|
|
||||||
|
tool_tips: {
|
||||||
|
double_click_to_edit: 'Doble clic para editar',
|
||||||
|
},
|
||||||
|
|
||||||
|
language_select: {
|
||||||
|
change_language: 'Idioma',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const esMetadata: LanguageMetadata = {
|
export const esMetadata: LanguageMetadata = {
|
||||||
name: 'Español',
|
name: 'Spanish',
|
||||||
|
nativeName: 'Español',
|
||||||
code: 'es',
|
code: 'es',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const fr: LanguageTranslation = {
|
|||||||
exit: 'Quitter',
|
exit: 'Quitter',
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Éditer',
|
edit: 'Édition',
|
||||||
undo: 'Annuler',
|
undo: 'Annuler',
|
||||||
redo: 'Rétablir',
|
redo: 'Rétablir',
|
||||||
clear: 'Effacer',
|
clear: 'Effacer',
|
||||||
@@ -28,10 +28,14 @@ export const fr: LanguageTranslation = {
|
|||||||
show_cardinality: 'Afficher la Cardinalité',
|
show_cardinality: 'Afficher la Cardinalité',
|
||||||
zoom_on_scroll: 'Zoom sur le Défilement',
|
zoom_on_scroll: 'Zoom sur le Défilement',
|
||||||
theme: 'Thème',
|
theme: 'Thème',
|
||||||
change_language: 'Langue',
|
|
||||||
show_dependencies: 'Afficher les Dépendances',
|
show_dependencies: 'Afficher les Dépendances',
|
||||||
hide_dependencies: 'Masquer les Dépendances',
|
hide_dependencies: 'Masquer les Dépendances',
|
||||||
},
|
},
|
||||||
|
share: {
|
||||||
|
share: 'Partage',
|
||||||
|
export_diagram: 'Exporter le diagramme',
|
||||||
|
import_diagram: 'Importer un diagramme',
|
||||||
|
},
|
||||||
help: {
|
help: {
|
||||||
help: 'Aide',
|
help: 'Aide',
|
||||||
visit_website: 'Visitez ChartDB',
|
visit_website: 'Visitez ChartDB',
|
||||||
@@ -130,6 +134,7 @@ export const fr: LanguageTranslation = {
|
|||||||
title: 'Actions de la Table',
|
title: 'Actions de la Table',
|
||||||
add_field: 'Ajouter un Champ',
|
add_field: 'Ajouter un Champ',
|
||||||
add_index: 'Ajouter un Index',
|
add_index: 'Ajouter un Index',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Supprimer la Table',
|
delete_table: 'Supprimer la Table',
|
||||||
change_schema: 'Changer le Schéma',
|
change_schema: 'Changer le Schéma',
|
||||||
},
|
},
|
||||||
@@ -218,6 +223,8 @@ export const fr: LanguageTranslation = {
|
|||||||
|
|
||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
back: 'Retour',
|
back: 'Retour',
|
||||||
|
// TODO: Translate
|
||||||
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'Diagramme vide',
|
empty_diagram: 'Diagramme vide',
|
||||||
continue: 'Continuer',
|
continue: 'Continuer',
|
||||||
import: 'Importer',
|
import: 'Importer',
|
||||||
@@ -332,7 +339,31 @@ export const fr: LanguageTranslation = {
|
|||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
export_diagram_dialog: {
|
||||||
|
title: 'Export Diagram',
|
||||||
|
description: 'Choose the format for export:',
|
||||||
|
format_json: 'JSON',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
export: 'Export',
|
||||||
|
error: {
|
||||||
|
title: 'Error exporting diagram',
|
||||||
|
description:
|
||||||
|
'Something went wrong. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
import_diagram_dialog: {
|
||||||
|
title: 'Import Diagram',
|
||||||
|
description: 'Paste the diagram JSON below:',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
import: 'Import',
|
||||||
|
error: {
|
||||||
|
title: 'Error importing diagram',
|
||||||
|
description:
|
||||||
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
one_to_one: 'Un à Un',
|
one_to_one: 'Un à Un',
|
||||||
one_to_many: 'Un à Plusieurs',
|
one_to_many: 'Un à Plusieurs',
|
||||||
@@ -347,12 +378,25 @@ export const fr: LanguageTranslation = {
|
|||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Éditer la Table',
|
edit_table: 'Éditer la Table',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Supprimer la Table',
|
delete_table: 'Supprimer la Table',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Add translations
|
||||||
|
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
||||||
|
|
||||||
|
tool_tips: {
|
||||||
|
double_click_to_edit: 'Double-cliquez pour modifier',
|
||||||
|
},
|
||||||
|
|
||||||
|
language_select: {
|
||||||
|
change_language: 'Langue',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const frMetadata: LanguageMetadata = {
|
export const frMetadata: LanguageMetadata = {
|
||||||
name: 'Français',
|
name: 'French',
|
||||||
|
nativeName: 'Français',
|
||||||
code: 'fr',
|
code: 'fr',
|
||||||
};
|
};
|
||||||
|
|||||||
397
src/i18n/locales/gu.ts
Normal file
@@ -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: 'कार्डिनैलिटी दिखाएँ',
|
show_cardinality: 'कार्डिनैलिटी दिखाएँ',
|
||||||
zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
|
zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
|
||||||
theme: 'थीम',
|
theme: 'थीम',
|
||||||
change_language: 'भाषा बदलें',
|
|
||||||
show_dependencies: 'निर्भरता दिखाएँ',
|
show_dependencies: 'निर्भरता दिखाएँ',
|
||||||
hide_dependencies: 'निर्भरता छिपाएँ',
|
hide_dependencies: 'निर्भरता छिपाएँ',
|
||||||
},
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
share: {
|
||||||
|
share: 'Share',
|
||||||
|
export_diagram: 'Export Diagram',
|
||||||
|
import_diagram: 'Import Diagram',
|
||||||
|
},
|
||||||
help: {
|
help: {
|
||||||
help: 'मदद',
|
help: 'मदद',
|
||||||
visit_website: 'ChartDB वेबसाइट पर जाएँ',
|
visit_website: 'ChartDB वेबसाइट पर जाएँ',
|
||||||
@@ -140,6 +145,7 @@ export const hi: LanguageTranslation = {
|
|||||||
change_schema: 'स्कीमा बदलें',
|
change_schema: 'स्कीमा बदलें',
|
||||||
add_field: 'फ़ील्ड जोड़ें',
|
add_field: 'फ़ील्ड जोड़ें',
|
||||||
add_index: 'सूचकांक जोड़ें',
|
add_index: 'सूचकांक जोड़ें',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'तालिका हटाएँ',
|
delete_table: 'तालिका हटाएँ',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -228,6 +234,8 @@ export const hi: LanguageTranslation = {
|
|||||||
|
|
||||||
cancel: 'रद्द करें',
|
cancel: 'रद्द करें',
|
||||||
back: 'वापस',
|
back: 'वापस',
|
||||||
|
// TODO: Translate
|
||||||
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'खाली आरेख',
|
empty_diagram: 'खाली आरेख',
|
||||||
continue: 'जारी रखें',
|
continue: 'जारी रखें',
|
||||||
import: 'आयात करें',
|
import: 'आयात करें',
|
||||||
@@ -331,7 +339,31 @@ export const hi: LanguageTranslation = {
|
|||||||
close: 'अभी नहीं',
|
close: 'अभी नहीं',
|
||||||
confirm: 'बिलकुल!',
|
confirm: 'बिलकुल!',
|
||||||
},
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
export_diagram_dialog: {
|
||||||
|
title: 'Export Diagram',
|
||||||
|
description: 'Choose the format for export:',
|
||||||
|
format_json: 'JSON',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
export: 'Export',
|
||||||
|
error: {
|
||||||
|
title: 'Error exporting diagram',
|
||||||
|
description:
|
||||||
|
'Something went wrong. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: Translate
|
||||||
|
import_diagram_dialog: {
|
||||||
|
title: 'Import Diagram',
|
||||||
|
description: 'Paste the diagram JSON below:',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
import: 'Import',
|
||||||
|
error: {
|
||||||
|
title: 'Error importing diagram',
|
||||||
|
description:
|
||||||
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
one_to_one: 'एक से एक',
|
one_to_one: 'एक से एक',
|
||||||
one_to_many: 'एक से कई',
|
one_to_many: 'एक से कई',
|
||||||
@@ -346,12 +378,25 @@ export const hi: LanguageTranslation = {
|
|||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'तालिका संपादित करें',
|
edit_table: 'तालिका संपादित करें',
|
||||||
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'तालिका हटाएँ',
|
delete_table: 'तालिका हटाएँ',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Add translations
|
||||||
|
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
||||||
|
|
||||||
|
tool_tips: {
|
||||||
|
double_click_to_edit: 'संपादित करने के लिए डबल-क्लिक करें',
|
||||||
|
},
|
||||||
|
|
||||||
|
language_select: {
|
||||||
|
change_language: 'भाषा बदलें',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hiMetadata: LanguageMetadata = {
|
export const hiMetadata: LanguageMetadata = {
|
||||||
name: 'Hindi',
|
name: 'Hindi',
|
||||||
|
nativeName: 'हिन्दी',
|
||||||
code: 'hi',
|
code: 'hi',
|
||||||
};
|
};
|
||||||
|
|||||||