Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86840a8822 | ||
|
|
487fb2d5c1 | ||
|
|
54d5e96a6d | ||
|
|
481ad3c844 | ||
|
|
0ce85cf76b | ||
|
|
5849e4586c | ||
|
|
34c0a7163f | ||
|
|
89e3ceab00 | ||
|
|
5a5e64abef | ||
|
|
2368e0d263 | ||
|
|
547149da44 | ||
|
|
a1144bbf76 | ||
|
|
6b8d637b75 | ||
|
|
fd47eb7f4b | ||
|
|
7db86dcf8c | ||
|
|
e75323c16e | ||
|
|
97d01d7201 | ||
|
|
90b42a4bb7 | ||
|
|
fbf2fe919c | ||
|
|
d3ddf7c51e | ||
|
|
5759241573 | ||
|
|
3747abbc3b | ||
|
|
226e6cf1ce | ||
|
|
1778abb683 | ||
|
|
90a20dd1b0 | ||
|
|
21c9129e14 | ||
|
|
19d2d0bddd | ||
|
|
83c43332d4 | ||
|
|
3a1b8d1db1 | ||
|
|
46426e27b4 | ||
|
|
9402822fa3 | ||
|
|
651fe361fc | ||
|
|
aee1713aec | ||
|
|
ecfa14829b | ||
|
|
92e3ec785c | ||
|
|
8102f19f79 | ||
|
|
840a00ebcd | ||
|
|
181f96d250 | ||
|
|
ce2389f135 | ||
|
|
f15dc77f33 | ||
|
|
caa81c24a6 | ||
|
|
e3cb62788c | ||
|
|
fc46cbb893 | ||
|
|
d94a71e9e1 | ||
|
|
cf81253535 | ||
|
|
25c4b42538 | ||
|
|
f7a6e0cb5e | ||
|
|
85275e5dd6 | ||
|
|
4e5b467ce5 | ||
|
|
874aa5ab75 | ||
|
|
0940d72d5d | ||
|
|
0d1739d70f |
33
.github/workflows/cla.yaml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: "CLA Assistant"
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened,closed,synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
contents: write # this can be 'read' if the signatures are in remote repository
|
||||||
|
pull-requests: write
|
||||||
|
statuses: write
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CLAAssistant:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "CLA Assistant"
|
||||||
|
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||||
|
# Beta Release
|
||||||
|
uses: contributor-assistant/github-action@v2.6.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.CHARTDB_CLA_SIGNATURES_PAT }}
|
||||||
|
with:
|
||||||
|
remote-organization-name: 'chartdb'
|
||||||
|
remote-repository-name: 'cla-signatures'
|
||||||
|
path-to-signatures: 'signatures/version1/cla.json'
|
||||||
|
path-to-document: 'https://github.com/chartdb/chartdb/blob/main/CLA.md'
|
||||||
|
# branch should not be protected
|
||||||
|
branch: 'main'
|
||||||
|
allowlist:
|
||||||
9
.github/workflows/publish.yaml
vendored
@@ -42,6 +42,12 @@ jobs:
|
|||||||
- name: Build project
|
- name: Build project
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
@@ -50,10 +56,11 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push multi-arch Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
58
CHANGELOG.md
@@ -1,5 +1,63 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.12.0](https://github.com/chartdb/chartdb/compare/v1.11.0...v1.12.0) (2025-05-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **areas:** implement area to enable logical diagram arrangement ([#661](https://github.com/chartdb/chartdb/issues/661)) ([92e3ec7](https://github.com/chartdb/chartdb/commit/92e3ec785c91f7f19881c6d9d0692257af4651bc))
|
||||||
|
* **examples:** update examples to have areas ([#677](https://github.com/chartdb/chartdb/issues/677)) ([21c9129](https://github.com/chartdb/chartdb/commit/21c9129e14670c744950cd43a5cbdd4b7d47c639))
|
||||||
|
* **image-export:** add transparent and pattern export image toggles ([#671](https://github.com/chartdb/chartdb/issues/671)) ([6b8d637](https://github.com/chartdb/chartdb/commit/6b8d637b757b94630ecd7521b4a2c99634afae69))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add sorting based on how common the datatype on side-panel ([#651](https://github.com/chartdb/chartdb/issues/651)) ([3a1b8d1](https://github.com/chartdb/chartdb/commit/3a1b8d1db13d8dd7cb6cbe5ef8c5a60faccfeae5))
|
||||||
|
* **canvas:** disable edit area name on read only ([#666](https://github.com/chartdb/chartdb/issues/666)) ([9402822](https://github.com/chartdb/chartdb/commit/9402822fa31f8cd94fe7971277839ee5425e29bf))
|
||||||
|
* **canvas:** read only mode ([#665](https://github.com/chartdb/chartdb/issues/665)) ([651fe36](https://github.com/chartdb/chartdb/commit/651fe361fce61fe0577d2593f268131e9ca359d0))
|
||||||
|
* **clone:** add areas to clone diagram ([#664](https://github.com/chartdb/chartdb/issues/664)) ([aee1713](https://github.com/chartdb/chartdb/commit/aee1713aecdd5e54228a16cbc3c4fc184661c56b))
|
||||||
|
* **dbml-editor:** add inline refs mode + fix issues with DBML syntax ([#687](https://github.com/chartdb/chartdb/issues/687)) ([fbf2fe9](https://github.com/chartdb/chartdb/commit/fbf2fe919c2168c715f8231c0246753b19635f14))
|
||||||
|
* **dbml-editor:** remove invalid fields before showing DBML + warning ([#683](https://github.com/chartdb/chartdb/issues/683)) ([5759241](https://github.com/chartdb/chartdb/commit/5759241573db204183c92599588d59f4aadaeafb))
|
||||||
|
* **ddl-import:** fix datatypes when importing via ddl ([#696](https://github.com/chartdb/chartdb/issues/696)) ([a1144bb](https://github.com/chartdb/chartdb/commit/a1144bbf761a0daedd546b5d9b92300be59e0157))
|
||||||
|
* **ddl:** inline fks ddl script ([#701](https://github.com/chartdb/chartdb/issues/701)) ([5849e45](https://github.com/chartdb/chartdb/commit/5849e4586c7c2a7cd86bd064df8916b130fc6234))
|
||||||
|
* **dependencies:** hide icon when diagram has no dependencies ([#684](https://github.com/chartdb/chartdb/issues/684)) ([547149d](https://github.com/chartdb/chartdb/commit/547149da44db6d3d1e36d619d475fe52ff83a472))
|
||||||
|
* **examples:** add loader ([#678](https://github.com/chartdb/chartdb/issues/678)) ([90a20dd](https://github.com/chartdb/chartdb/commit/90a20dd1b0277c4aee848fae5ed7a8347c5ba77d))
|
||||||
|
* **examples:** fix clone examples ([#679](https://github.com/chartdb/chartdb/issues/679)) ([1778abb](https://github.com/chartdb/chartdb/commit/1778abb683d575af244edcd9a11f8d03f903f719))
|
||||||
|
* **expanded-table:** persist expanded state across renders ([#707](https://github.com/chartdb/chartdb/issues/707)) ([54d5e96](https://github.com/chartdb/chartdb/commit/54d5e96a6db1e3abd52229a89ac503ff31885386))
|
||||||
|
* **export image:** Fix usage of advanced options accordion ([#703](https://github.com/chartdb/chartdb/issues/703)) ([0ce85cf](https://github.com/chartdb/chartdb/commit/0ce85cf76b733f441f661608278c0db3122c5074))
|
||||||
|
* **import-database:** auto detect when user try to import ddl script ([#698](https://github.com/chartdb/chartdb/issues/698)) ([5a5e64a](https://github.com/chartdb/chartdb/commit/5a5e64abef510cff28b3d8972520d0b9df29b024))
|
||||||
|
* **import-database:** remove view_definition when importing via query ([#702](https://github.com/chartdb/chartdb/issues/702)) ([481ad3c](https://github.com/chartdb/chartdb/commit/481ad3c8449f469bf2b4418e4cdcc5b5608dfd36))
|
||||||
|
* **import-json:** for broken json imports ([#697](https://github.com/chartdb/chartdb/issues/697)) ([2368e0d](https://github.com/chartdb/chartdb/commit/2368e0d2639021c4a11a8e5131d6af44fb6a47db))
|
||||||
|
* **import-json:** simplify import script for fixing invalid JSON ([#681](https://github.com/chartdb/chartdb/issues/681)) ([226e6cf](https://github.com/chartdb/chartdb/commit/226e6cf1ce4d2edcfbee6a4de7ab0bc0cfeb17fe))
|
||||||
|
* **import:** dbml and query - senetize before import ([#699](https://github.com/chartdb/chartdb/issues/699)) ([34c0a71](https://github.com/chartdb/chartdb/commit/34c0a7163f47bde7ddfaa8f044341e3c971b7e03))
|
||||||
|
* **navbar:** open diagram directly from diagram icon ([#694](https://github.com/chartdb/chartdb/issues/694)) ([7db86dc](https://github.com/chartdb/chartdb/commit/7db86dcf8c97d34b056e4b5b85a0dda0438322ea))
|
||||||
|
* **performance:** Only render visible ([#672](https://github.com/chartdb/chartdb/issues/672)) ([83c4333](https://github.com/chartdb/chartdb/commit/83c43332d497e9fc148a18b9cb4d9ecc85e44183))
|
||||||
|
* **performance:** update field only when changed ([#685](https://github.com/chartdb/chartdb/issues/685)) ([d3ddf7c](https://github.com/chartdb/chartdb/commit/d3ddf7c51eaa4b9cddb961defd52d423f39f281d))
|
||||||
|
* **postgres:** fix import of postgres fks ([#700](https://github.com/chartdb/chartdb/issues/700)) ([89e3cea](https://github.com/chartdb/chartdb/commit/89e3ceab00defaabc079e165fc90e92ca00722cf))
|
||||||
|
* **schema:** add areas to diagram schema ([#663](https://github.com/chartdb/chartdb/issues/663)) ([ecfa148](https://github.com/chartdb/chartdb/commit/ecfa14829bcb1b813c7b154b4bd59f24e3032d8f))
|
||||||
|
* **sql-script:** change ddl to be sql-script ([#710](https://github.com/chartdb/chartdb/issues/710)) ([487fb2d](https://github.com/chartdb/chartdb/commit/487fb2d5c17b70ac54aa17af9a2ac9aded6b40ba))
|
||||||
|
* **table:** enhance field focus behavior to include table hover state ([#676](https://github.com/chartdb/chartdb/issues/676)) ([19d2d0b](https://github.com/chartdb/chartdb/commit/19d2d0bddd3a464995b79e97e6caf6e652836081))
|
||||||
|
* **translations:** Add some translations for ru-RU language ([#690](https://github.com/chartdb/chartdb/issues/690)) ([97d01d7](https://github.com/chartdb/chartdb/commit/97d01d72014e473c42348c9ebcbe7a0b973d31aa))
|
||||||
|
|
||||||
|
## [1.11.0](https://github.com/chartdb/chartdb/compare/v1.10.0...v1.11.0) (2025-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add sidebar footer help buttons ([#650](https://github.com/chartdb/chartdb/issues/650)) ([fc46cbb](https://github.com/chartdb/chartdb/commit/fc46cbb8933761c7bac3604664f7de812f6f5b6b))
|
||||||
|
* **import-sql:** import postgresql via SQL (DDL script) ([#639](https://github.com/chartdb/chartdb/issues/639)) ([f7a6e0c](https://github.com/chartdb/chartdb/commit/f7a6e0cb5e4921dd9540739f9da269858e7ca7be))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **import:** display query result formatted ([#644](https://github.com/chartdb/chartdb/issues/644)) ([caa81c2](https://github.com/chartdb/chartdb/commit/caa81c24a6535bc87129c38622aac5a62a6d479d))
|
||||||
|
* **import:** strict parse of database metadata ([#635](https://github.com/chartdb/chartdb/issues/635)) ([0940d72](https://github.com/chartdb/chartdb/commit/0940d72d5d3726650213257639f24ba47e729854))
|
||||||
|
* **mobile:** fix create diagram modal on mobile ([#646](https://github.com/chartdb/chartdb/issues/646)) ([25c4b42](https://github.com/chartdb/chartdb/commit/25c4b4253849575d7a781ed197281e2a35e7184a))
|
||||||
|
* **mysql-ddl:** update the script to import - for create fks ([#642](https://github.com/chartdb/chartdb/issues/642)) ([cf81253](https://github.com/chartdb/chartdb/commit/cf81253535ca5a3b8a65add78287c1bdb283a1c7))
|
||||||
|
* **performance:** Import deps dynamically ([#652](https://github.com/chartdb/chartdb/issues/652)) ([e3cb627](https://github.com/chartdb/chartdb/commit/e3cb62788c13f149e35e1a5020191bd43d14b52f))
|
||||||
|
* remove unused links from help menu ([#623](https://github.com/chartdb/chartdb/issues/623)) ([85275e5](https://github.com/chartdb/chartdb/commit/85275e5dd6e7845f06f682eeceda7932fc87e875))
|
||||||
|
* **sidebar:** turn sidebar to responsive for mobile ([#658](https://github.com/chartdb/chartdb/issues/658)) ([ce2389f](https://github.com/chartdb/chartdb/commit/ce2389f135d399d82c9848335d31174bac8a3791))
|
||||||
|
|
||||||
## [1.10.0](https://github.com/chartdb/chartdb/compare/v1.9.0...v1.10.0) (2025-03-25)
|
## [1.10.0](https://github.com/chartdb/chartdb/compare/v1.9.0...v1.10.0) (2025-03-25)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
45
CLA.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# ChartDB Contributors License Agreement
|
||||||
|
|
||||||
|
This Contributors License Agreement ("CLA") is entered into between the Contributor, and ChartDB, Inc. ("ChartDB"), collectively referred to as the "Parties."
|
||||||
|
|
||||||
|
## Background:
|
||||||
|
|
||||||
|
ChartDB is an open-source project aimed at providing an open-source database diagramming and visualization tool for all parties.This CLA governs the rights and contributions made by the Contributor to the ChartDB project.
|
||||||
|
|
||||||
|
## Agreement:
|
||||||
|
|
||||||
|
**Contributor Grant of License:**
|
||||||
|
|
||||||
|
By submitting code, documentation, or any other materials (collectively, "Contributions") to the ChartDB project, the Contributor grants ChartDB a perpetual, worldwide, non-exclusive, royalty-free, sublicensable license to use, modify, distribute, and otherwise exploit the Contributions, including any intellectual property rights therein, for the purposes of the ChartDB project.
|
||||||
|
|
||||||
|
**Representation of Ownership and Right to Contribute:**
|
||||||
|
|
||||||
|
The Contributor represents that they have the legal right to grant the license stated in Section 1, and that the Contributions do not infringe upon the intellectual property rights of any third party. The Contributor also represents that they have the authority to submit the Contributions on their own behalf or, if applicable, on behalf of their employer or any other entity.
|
||||||
|
|
||||||
|
**Patent Grant:**
|
||||||
|
|
||||||
|
If the Contributions include any method, process, or apparatus that is covered by a patent, the Contributor agrees to grant ChartDB a non-exclusive, worldwide, royalty-free license under any patent claims necessary to use, modify, distribute, and otherwise exploit the Contributions for the purposes of the ChartDB project.
|
||||||
|
|
||||||
|
**No Implied Warranties or Support:**
|
||||||
|
|
||||||
|
The Contributor acknowledges that the Contributions are provided "as is," without any warranties or support of any kind. ChartDB shall have no obligation to provide maintenance, updates, bug fixes, or support for the Contributions.
|
||||||
|
|
||||||
|
**Retention of Contributor Rights:**
|
||||||
|
|
||||||
|
The Contributor retains all right, title, and interest in and to their Contributions. This CLA does not restrict the Contributor from using their own Contributions for any other purpose.
|
||||||
|
|
||||||
|
**Governing Law:**
|
||||||
|
|
||||||
|
This CLA shall be governed by and construed in accordance with the laws of Delaware (DE), without regard to its conflict of laws principles.
|
||||||
|
|
||||||
|
**Entire Agreement:**
|
||||||
|
|
||||||
|
This CLA constitutes the entire agreement between the Parties with respect to the subject matter hereof and supersedes all prior and contemporaneous understandings, agreements, representations, and warranties.
|
||||||
|
|
||||||
|
**Acceptance:**
|
||||||
|
|
||||||
|
By submitting Contributions to the ChartDB project, the Contributor acknowledges and agrees to the terms and conditions of this CLA. If the Contributor is agreeing to this CLA on behalf of an entity, they represent that they have the necessary authority to bind that entity to these terms.
|
||||||
|
|
||||||
|
**Effective Date:**
|
||||||
|
|
||||||
|
This CLA is effective as of the date of the first Contribution made by the Contributor to the ChartDB project.
|
||||||
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement at
|
||||||
chartdb.io@gmail.com.
|
support@chartdb.io.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ To submit a pull request:
|
|||||||
|
|
||||||
If you find a bug, check [GitHub issues](https://github.com/chartdb/chartdb/issues) to see if it’s already reported. If not, feel free to [report it](https://github.com/chartdb/chartdb/issues/new?labels=bug).
|
If you find a bug, check [GitHub issues](https://github.com/chartdb/chartdb/issues) to see if it’s already reported. If not, feel free to [report it](https://github.com/chartdb/chartdb/issues/new?labels=bug).
|
||||||
|
|
||||||
For questions about using ChartDB, reach out to us via Email (chartdb.io@gmail.com) or [Discord](https://discord.gg/QeFwyWSKwC). For feature requests, create a [new feature](https://github.com/chartdb/chartdb/issues/new?labels=enhancement).
|
For questions about using ChartDB, reach out to us via Email (support@chartdb.io) or [Discord](https://discord.gg/QeFwyWSKwC). For feature requests, create a [new feature](https://github.com/chartdb/chartdb/issues/new?labels=enhancement).
|
||||||
|
|
||||||
### Creating a Branch
|
### Creating a Branch
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ By contributing, you agree that your work will be licensed under ChartDB's [lice
|
|||||||
## Questions?
|
## Questions?
|
||||||
|
|
||||||
Feel free to ask in `#contributing` on [Discord](https://discord.gg/QeFwyWSKwC) if you have questions about our process, how to proceed, etc.
|
Feel free to ask in `#contributing` on [Discord](https://discord.gg/QeFwyWSKwC) if you have questions about our process, how to proceed, etc.
|
||||||
or [Email](chartdb.io@gmail.com)
|
or [Email](support@chartdb.io)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
<a href="https://discord.gg/QeFwyWSKwC">
|
<a href="https://discord.gg/QeFwyWSKwC">
|
||||||
<img src="https://img.shields.io/discord/1277047413705670678?color=5865F2&label=Discord&logo=discord&logoColor=white" alt="Discord community channel" />
|
<img src="https://img.shields.io/discord/1277047413705670678?color=5865F2&label=Discord&logo=discord&logoColor=white" alt="Discord community channel" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://x.com/chartdb_io">
|
<a href="https://x.com/intent/follow?screen_name=jonathanfishner">
|
||||||
<img src="https://img.shields.io/twitter/follow/ChartDB?style=social"/>
|
<img src="https://img.shields.io/twitter/follow/jonathanfishner?style=social"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</h4>
|
</h4>
|
||||||
@@ -149,7 +149,7 @@ VITE_LLM_MODEL_NAME=Qwen/Qwen2.5-32B-Instruct-AWQ
|
|||||||
|
|
||||||
- [Discord](https://discord.gg/QeFwyWSKwC) (For live discussion with the community and the ChartDB team)
|
- [Discord](https://discord.gg/QeFwyWSKwC) (For live discussion with the community and the ChartDB team)
|
||||||
- [GitHub Issues](https://github.com/chartdb/chartdb/issues) (For any bugs and errors you encounter using ChartDB)
|
- [GitHub Issues](https://github.com/chartdb/chartdb/issues) (For any bugs and errors you encounter using ChartDB)
|
||||||
- [Twitter](https://x.com/chartdb_io) (Get news fast)
|
- [Twitter](https://x.com/intent/follow?screen_name=jonathanfishner) (Get news fast)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"version": "1.10.0",
|
"version": "1.12.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"version": "1.10.0",
|
"version": "1.12.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.51",
|
"@ai-sdk/openai": "^0.0.51",
|
||||||
"@dbml/core": "^3.9.5",
|
"@dbml/core": "^3.9.5",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.10.0",
|
"version": "1.12.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 416 KiB After Width: | Height: | Size: 482 KiB |
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 434 KiB |
|
Before Width: | Height: | Size: 441 KiB After Width: | Height: | Size: 543 KiB |
|
Before Width: | Height: | Size: 405 KiB After Width: | Height: | Size: 488 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 404 KiB |
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 359 KiB |
@@ -1,2 +1,3 @@
|
|||||||
import './config.ts';
|
import './config.ts';
|
||||||
export { Editor } from '@monaco-editor/react';
|
export { Editor } from '@monaco-editor/react';
|
||||||
|
export { DiffEditor } from '@monaco-editor/react';
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTheme } from '@/hooks/use-theme';
|
|||||||
import { useMonaco } from '@monaco-editor/react';
|
import { useMonaco } from '@monaco-editor/react';
|
||||||
import { useToast } from '@/components/toast/use-toast';
|
import { useToast } from '@/components/toast/use-toast';
|
||||||
import { Button } from '../button/button';
|
import { Button } from '../button/button';
|
||||||
|
import type { LucideIcon } from 'lucide-react';
|
||||||
import { Copy, CopyCheck } from 'lucide-react';
|
import { Copy, CopyCheck } from 'lucide-react';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -18,27 +19,43 @@ export const Editor = lazy(() =>
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const DiffEditor = lazy(() =>
|
||||||
|
import('./code-editor').then((module) => ({
|
||||||
|
default: module.DiffEditor,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
type EditorType = typeof Editor;
|
type EditorType = typeof Editor;
|
||||||
|
|
||||||
|
export interface CodeSnippetAction {
|
||||||
|
label: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CodeSnippetProps {
|
export interface CodeSnippetProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
code: string;
|
code: string;
|
||||||
|
codeToCopy?: string;
|
||||||
language?: 'sql' | 'shell';
|
language?: 'sql' | 'shell';
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
autoScroll?: boolean;
|
autoScroll?: boolean;
|
||||||
isComplete?: boolean;
|
isComplete?: boolean;
|
||||||
editorProps?: React.ComponentProps<EditorType>;
|
editorProps?: React.ComponentProps<EditorType>;
|
||||||
|
actions?: CodeSnippetAction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||||
({
|
({
|
||||||
className,
|
className,
|
||||||
code,
|
code,
|
||||||
|
codeToCopy,
|
||||||
loading,
|
loading,
|
||||||
language = 'sql',
|
language = 'sql',
|
||||||
autoScroll = false,
|
autoScroll = false,
|
||||||
isComplete = true,
|
isComplete = true,
|
||||||
editorProps,
|
editorProps,
|
||||||
|
actions,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const monaco = useMonaco();
|
const monaco = useMonaco();
|
||||||
@@ -85,7 +102,7 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(code);
|
await navigator.clipboard.writeText(codeToCopy ?? code);
|
||||||
setIsCopied(true);
|
setIsCopied(true);
|
||||||
} catch {
|
} catch {
|
||||||
setIsCopied(false);
|
setIsCopied(false);
|
||||||
@@ -97,7 +114,7 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [code, t, toast]);
|
}, [code, codeToCopy, t, toast]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -111,17 +128,15 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
) : (
|
) : (
|
||||||
<Suspense fallback={<Spinner />}>
|
<Suspense fallback={<Spinner />}>
|
||||||
{isComplete ? (
|
{isComplete ? (
|
||||||
|
<div className="absolute right-1 top-1 z-10 flex flex-col gap-1">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
onOpenChange={setTooltipOpen}
|
onOpenChange={setTooltipOpen}
|
||||||
open={isCopied || tooltipOpen}
|
open={isCopied || tooltipOpen}
|
||||||
>
|
>
|
||||||
<TooltipTrigger
|
<TooltipTrigger asChild>
|
||||||
asChild
|
|
||||||
className="absolute right-1 top-1 z-10"
|
|
||||||
>
|
|
||||||
<span>
|
<span>
|
||||||
<Button
|
<Button
|
||||||
className=" h-fit p-1.5"
|
className="h-fit p-1.5"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={copyToClipboard}
|
onClick={copyToClipboard}
|
||||||
>
|
>
|
||||||
@@ -141,6 +156,30 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
)}
|
)}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
{actions &&
|
||||||
|
actions.length > 0 &&
|
||||||
|
actions.map((action, index) => (
|
||||||
|
<Tooltip key={index}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
className="h-fit p-1.5"
|
||||||
|
variant="outline"
|
||||||
|
onClick={action.onClick}
|
||||||
|
>
|
||||||
|
<action.icon
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{action.label}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Editor
|
<Editor
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface DiagramIconProps
|
|||||||
export const DiagramIcon = React.forwardRef<
|
export const DiagramIcon = React.forwardRef<
|
||||||
React.ElementRef<typeof TooltipTrigger>,
|
React.ElementRef<typeof TooltipTrigger>,
|
||||||
DiagramIconProps
|
DiagramIconProps
|
||||||
>(({ databaseType, databaseEdition, className, imgClassName }, ref) =>
|
>(({ databaseType, databaseEdition, className, imgClassName, onClick }, ref) =>
|
||||||
databaseEdition ? (
|
databaseEdition ? (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger className={cn('mr-1', className)} ref={ref} asChild>
|
<TooltipTrigger className={cn('mr-1', className)} ref={ref} asChild>
|
||||||
@@ -30,6 +30,7 @@ export const DiagramIcon = React.forwardRef<
|
|||||||
src={databaseEditionToImageMap[databaseEdition]}
|
src={databaseEditionToImageMap[databaseEdition]}
|
||||||
className={cn('h-5 max-w-fit rounded-full', imgClassName)}
|
className={cn('h-5 max-w-fit rounded-full', imgClassName)}
|
||||||
alt="database"
|
alt="database"
|
||||||
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -43,6 +44,7 @@ export const DiagramIcon = React.forwardRef<
|
|||||||
src={databaseSecondaryLogoMap[databaseType]}
|
src={databaseSecondaryLogoMap[databaseType]}
|
||||||
className={cn('h-5 max-w-fit', imgClassName)}
|
className={cn('h-5 max-w-fit', imgClassName)}
|
||||||
alt="database"
|
alt="database"
|
||||||
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
|
|||||||
@@ -345,7 +345,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
? [option.regex]
|
? [option.regex]
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
// value={option.value}
|
|
||||||
onSelect={() =>
|
onSelect={() =>
|
||||||
handleSelect(
|
handleSelect(
|
||||||
option.value,
|
option.value,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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 type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import { EventEmitter } from 'ahooks/lib/useEventEmitter';
|
import { EventEmitter } from 'ahooks/lib/useEventEmitter';
|
||||||
|
import type { Area } from '@/lib/domain/area';
|
||||||
|
|
||||||
export type ChartDBEventType =
|
export type ChartDBEventType =
|
||||||
| 'add_tables'
|
| 'add_tables'
|
||||||
@@ -70,6 +71,7 @@ export interface ChartDBContext {
|
|||||||
schemas: DBSchema[];
|
schemas: DBSchema[];
|
||||||
relationships: DBRelationship[];
|
relationships: DBRelationship[];
|
||||||
dependencies: DBDependency[];
|
dependencies: DBDependency[];
|
||||||
|
areas: Area[];
|
||||||
currentDiagram: Diagram;
|
currentDiagram: Diagram;
|
||||||
events: EventEmitter<ChartDBEvent>;
|
events: EventEmitter<ChartDBEvent>;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
@@ -221,6 +223,31 @@ export interface ChartDBContext {
|
|||||||
dependency: Partial<DBDependency>,
|
dependency: Partial<DBDependency>,
|
||||||
options?: { updateHistory: boolean }
|
options?: { updateHistory: boolean }
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
|
// Area operations
|
||||||
|
createArea: (attributes?: Partial<Omit<Area, 'id'>>) => Promise<Area>;
|
||||||
|
addArea: (
|
||||||
|
area: Area,
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
addAreas: (
|
||||||
|
areas: Area[],
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
getArea: (id: string) => Area | null;
|
||||||
|
removeArea: (
|
||||||
|
id: string,
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
removeAreas: (
|
||||||
|
ids: string[],
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
updateArea: (
|
||||||
|
id: string,
|
||||||
|
area: Partial<Area>,
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const chartDBContext = createContext<ChartDBContext>({
|
export const chartDBContext = createContext<ChartDBContext>({
|
||||||
@@ -230,6 +257,7 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
tables: [],
|
tables: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
|
areas: [],
|
||||||
schemas: [],
|
schemas: [],
|
||||||
filteredSchemas: [],
|
filteredSchemas: [],
|
||||||
filterSchemas: emptyFn,
|
filterSchemas: emptyFn,
|
||||||
@@ -296,4 +324,13 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
removeDependencies: emptyFn,
|
removeDependencies: emptyFn,
|
||||||
addDependencies: emptyFn,
|
addDependencies: emptyFn,
|
||||||
updateDependency: emptyFn,
|
updateDependency: emptyFn,
|
||||||
|
|
||||||
|
// Area operations
|
||||||
|
createArea: emptyFn,
|
||||||
|
addArea: emptyFn,
|
||||||
|
addAreas: emptyFn,
|
||||||
|
getArea: emptyFn,
|
||||||
|
removeArea: emptyFn,
|
||||||
|
removeAreas: emptyFn,
|
||||||
|
updateArea: emptyFn,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { useLocalConfig } from '@/hooks/use-local-config';
|
|||||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
import { useEventEmitter } from 'ahooks';
|
import { useEventEmitter } from 'ahooks';
|
||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
|
import type { Area } from '@/lib/domain/area';
|
||||||
import { storageInitialValue } from '../storage-context/storage-context';
|
import { storageInitialValue } from '../storage-context/storage-context';
|
||||||
import { useDiff } from '../diff-context/use-diff';
|
import { useDiff } from '../diff-context/use-diff';
|
||||||
import type { DiffCalculatedEvent } from '../diff-context/diff-context';
|
import type { DiffCalculatedEvent } from '../diff-context/diff-context';
|
||||||
@@ -56,6 +57,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
const [dependencies, setDependencies] = useState<DBDependency[]>(
|
const [dependencies, setDependencies] = useState<DBDependency[]>(
|
||||||
diagram?.dependencies ?? []
|
diagram?.dependencies ?? []
|
||||||
);
|
);
|
||||||
|
const [areas, setAreas] = useState<Area[]>(diagram?.areas ?? []);
|
||||||
const { events: diffEvents } = useDiff();
|
const { events: diffEvents } = useDiff();
|
||||||
|
|
||||||
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
|
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
|
||||||
@@ -152,6 +154,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
tables,
|
tables,
|
||||||
relationships,
|
relationships,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
areas,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
diagramId,
|
diagramId,
|
||||||
@@ -161,6 +164,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
tables,
|
tables,
|
||||||
relationships,
|
relationships,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
areas,
|
||||||
diagramCreatedAt,
|
diagramCreatedAt,
|
||||||
diagramUpdatedAt,
|
diagramUpdatedAt,
|
||||||
]
|
]
|
||||||
@@ -172,6 +176,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
setTables([]);
|
setTables([]);
|
||||||
setRelationships([]);
|
setRelationships([]);
|
||||||
setDependencies([]);
|
setDependencies([]);
|
||||||
|
setAreas([]);
|
||||||
setDiagramUpdatedAt(updatedAt);
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
resetRedoStack();
|
resetRedoStack();
|
||||||
@@ -182,6 +187,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
db.deleteDiagramTables(diagramId),
|
db.deleteDiagramTables(diagramId),
|
||||||
db.deleteDiagramRelationships(diagramId),
|
db.deleteDiagramRelationships(diagramId),
|
||||||
db.deleteDiagramDependencies(diagramId),
|
db.deleteDiagramDependencies(diagramId),
|
||||||
|
db.deleteDiagramAreas(diagramId),
|
||||||
]);
|
]);
|
||||||
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
||||||
|
|
||||||
@@ -194,6 +200,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
setTables([]);
|
setTables([]);
|
||||||
setRelationships([]);
|
setRelationships([]);
|
||||||
setDependencies([]);
|
setDependencies([]);
|
||||||
|
setAreas([]);
|
||||||
resetRedoStack();
|
resetRedoStack();
|
||||||
resetUndoStack();
|
resetUndoStack();
|
||||||
|
|
||||||
@@ -202,6 +209,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
db.deleteDiagramRelationships(diagramId),
|
db.deleteDiagramRelationships(diagramId),
|
||||||
db.deleteDiagram(diagramId),
|
db.deleteDiagram(diagramId),
|
||||||
db.deleteDiagramDependencies(diagramId),
|
db.deleteDiagramDependencies(diagramId),
|
||||||
|
db.deleteDiagramAreas(diagramId),
|
||||||
]);
|
]);
|
||||||
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
||||||
|
|
||||||
@@ -1363,6 +1371,130 @@ export const ChartDBProvider: React.FC<
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Area operations
|
||||||
|
const addAreas: ChartDBContext['addAreas'] = useCallback(
|
||||||
|
async (areas: Area[], options = { updateHistory: true }) => {
|
||||||
|
setAreas((currentAreas) => [...currentAreas, ...areas]);
|
||||||
|
|
||||||
|
const updatedAt = new Date();
|
||||||
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
...areas.map((area) => db.addArea({ diagramId, area })),
|
||||||
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (options.updateHistory) {
|
||||||
|
addUndoAction({
|
||||||
|
action: 'addAreas',
|
||||||
|
redoData: { areas },
|
||||||
|
undoData: { areaIds: areas.map((a) => a.id) },
|
||||||
|
});
|
||||||
|
resetRedoStack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[db, diagramId, setAreas, addUndoAction, resetRedoStack]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addArea: ChartDBContext['addArea'] = useCallback(
|
||||||
|
async (area: Area, options = { updateHistory: true }) => {
|
||||||
|
return addAreas([area], options);
|
||||||
|
},
|
||||||
|
[addAreas]
|
||||||
|
);
|
||||||
|
|
||||||
|
const createArea: ChartDBContext['createArea'] = useCallback(
|
||||||
|
async (attributes) => {
|
||||||
|
const area: Area = {
|
||||||
|
id: generateId(),
|
||||||
|
name: `Area ${areas.length + 1}`,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
color: randomColor(),
|
||||||
|
...attributes,
|
||||||
|
};
|
||||||
|
|
||||||
|
await addArea(area);
|
||||||
|
|
||||||
|
return area;
|
||||||
|
},
|
||||||
|
[areas, addArea]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getArea: ChartDBContext['getArea'] = useCallback(
|
||||||
|
(id: string) => areas.find((area) => area.id === id) ?? null,
|
||||||
|
[areas]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeAreas: ChartDBContext['removeAreas'] = useCallback(
|
||||||
|
async (ids: string[], options = { updateHistory: true }) => {
|
||||||
|
const prevAreas = [
|
||||||
|
...areas.filter((area) => ids.includes(area.id)),
|
||||||
|
];
|
||||||
|
|
||||||
|
setAreas((areas) => areas.filter((area) => !ids.includes(area.id)));
|
||||||
|
|
||||||
|
const updatedAt = new Date();
|
||||||
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
...ids.map((id) => db.deleteArea({ diagramId, id })),
|
||||||
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (prevAreas.length > 0 && options.updateHistory) {
|
||||||
|
addUndoAction({
|
||||||
|
action: 'removeAreas',
|
||||||
|
redoData: { areaIds: ids },
|
||||||
|
undoData: { areas: prevAreas },
|
||||||
|
});
|
||||||
|
resetRedoStack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[db, diagramId, setAreas, areas, addUndoAction, resetRedoStack]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeArea: ChartDBContext['removeArea'] = useCallback(
|
||||||
|
async (id: string, options = { updateHistory: true }) => {
|
||||||
|
return removeAreas([id], options);
|
||||||
|
},
|
||||||
|
[removeAreas]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateArea: ChartDBContext['updateArea'] = useCallback(
|
||||||
|
async (
|
||||||
|
id: string,
|
||||||
|
area: Partial<Area>,
|
||||||
|
options = { updateHistory: true }
|
||||||
|
) => {
|
||||||
|
const prevArea = getArea(id);
|
||||||
|
|
||||||
|
setAreas((areas) =>
|
||||||
|
areas.map((a) => (a.id === id ? { ...a, ...area } : a))
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedAt = new Date();
|
||||||
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
||||||
|
db.updateArea({ id, attributes: area }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!!prevArea && options.updateHistory) {
|
||||||
|
addUndoAction({
|
||||||
|
action: 'updateArea',
|
||||||
|
redoData: { areaId: id, area },
|
||||||
|
undoData: { areaId: id, area: prevArea },
|
||||||
|
});
|
||||||
|
resetRedoStack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack]
|
||||||
|
);
|
||||||
|
|
||||||
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
|
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
|
||||||
useCallback(
|
useCallback(
|
||||||
async (diagram) => {
|
async (diagram) => {
|
||||||
@@ -1373,6 +1505,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
setTables(diagram?.tables ?? []);
|
setTables(diagram?.tables ?? []);
|
||||||
setRelationships(diagram?.relationships ?? []);
|
setRelationships(diagram?.relationships ?? []);
|
||||||
setDependencies(diagram?.dependencies ?? []);
|
setDependencies(diagram?.dependencies ?? []);
|
||||||
|
setAreas(diagram?.areas ?? []);
|
||||||
setDiagramCreatedAt(diagram.createdAt);
|
setDiagramCreatedAt(diagram.createdAt);
|
||||||
setDiagramUpdatedAt(diagram.updatedAt);
|
setDiagramUpdatedAt(diagram.updatedAt);
|
||||||
|
|
||||||
@@ -1386,6 +1519,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
setTables,
|
setTables,
|
||||||
setRelationships,
|
setRelationships,
|
||||||
setDependencies,
|
setDependencies,
|
||||||
|
setAreas,
|
||||||
setDiagramCreatedAt,
|
setDiagramCreatedAt,
|
||||||
setDiagramUpdatedAt,
|
setDiagramUpdatedAt,
|
||||||
events,
|
events,
|
||||||
@@ -1398,6 +1532,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
includeRelationships: true,
|
includeRelationships: true,
|
||||||
includeTables: true,
|
includeTables: true,
|
||||||
includeDependencies: true,
|
includeDependencies: true,
|
||||||
|
includeAreas: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (diagram) {
|
if (diagram) {
|
||||||
@@ -1418,6 +1553,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
tables,
|
tables,
|
||||||
relationships,
|
relationships,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
areas,
|
||||||
currentDiagram,
|
currentDiagram,
|
||||||
schemas,
|
schemas,
|
||||||
filteredSchemas,
|
filteredSchemas,
|
||||||
@@ -1465,6 +1601,13 @@ export const ChartDBProvider: React.FC<
|
|||||||
removeDependency,
|
removeDependency,
|
||||||
removeDependencies,
|
removeDependencies,
|
||||||
updateDependency,
|
updateDependency,
|
||||||
|
createArea,
|
||||||
|
addArea,
|
||||||
|
addAreas,
|
||||||
|
getArea,
|
||||||
|
removeArea,
|
||||||
|
removeAreas,
|
||||||
|
updateArea,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
|||||||
|
|
||||||
export interface ConfigContext {
|
export interface ConfigContext {
|
||||||
config?: ChartDBConfig;
|
config?: ChartDBConfig;
|
||||||
updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>;
|
updateConfig: (params: {
|
||||||
|
config?: Partial<ChartDBConfig>;
|
||||||
|
updateFn?: (config: ChartDBConfig) => ChartDBConfig;
|
||||||
|
}) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfigContext = createContext<ConfigContext>({
|
export const ConfigContext = createContext<ConfigContext>({
|
||||||
|
|||||||
@@ -19,15 +19,29 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
loadConfig();
|
loadConfig();
|
||||||
}, [getConfig]);
|
}, [getConfig]);
|
||||||
|
|
||||||
const updateConfig: ConfigContext['updateConfig'] = async (
|
const updateConfig: ConfigContext['updateConfig'] = async ({
|
||||||
config: Partial<ChartDBConfig>
|
config,
|
||||||
) => {
|
updateFn,
|
||||||
await updateDataConfig(config);
|
}) => {
|
||||||
setConfig((prevConfig) =>
|
const promise = new Promise<void>((resolve) => {
|
||||||
prevConfig
|
setConfig((prevConfig) => {
|
||||||
? { ...prevConfig, ...config }
|
let baseConfig: ChartDBConfig = { defaultDiagramId: '' };
|
||||||
: { ...{ defaultDiagramId: '' }, ...config }
|
if (prevConfig) {
|
||||||
);
|
baseConfig = prevConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedConfig = updateFn
|
||||||
|
? updateFn(baseConfig)
|
||||||
|
: { ...baseConfig, ...config };
|
||||||
|
|
||||||
|
updateDataConfig(updatedConfig).then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
return updatedConfig;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/i
|
|||||||
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||||
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
||||||
import type { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
import type { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
||||||
|
import type { CreateDiagramDialogProps } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
|
||||||
|
|
||||||
export interface DialogContext {
|
export interface DialogContext {
|
||||||
// Create diagram dialog
|
// Create diagram dialog
|
||||||
openCreateDiagramDialog: () => void;
|
openCreateDiagramDialog: (
|
||||||
|
params?: Omit<CreateDiagramDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
closeCreateDiagramDialog: () => void;
|
closeCreateDiagramDialog: () => void;
|
||||||
|
|
||||||
// Open diagram dialog
|
// Open diagram dialog
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import type { DialogContext } from './dialog-context';
|
import type { DialogContext } from './dialog-context';
|
||||||
import { dialogContext } from './dialog-context';
|
import { dialogContext } from './dialog-context';
|
||||||
|
import type { CreateDiagramDialogProps } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
|
||||||
import { CreateDiagramDialog } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
|
import { CreateDiagramDialog } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
|
||||||
import type { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
import type { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
||||||
import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
||||||
@@ -26,6 +27,17 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false);
|
const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false);
|
||||||
|
const [newDiagramDialogParams, setNewDiagramDialogParams] =
|
||||||
|
useState<Omit<CreateDiagramDialogProps, 'dialog'>>();
|
||||||
|
const openNewDiagramDialogHandler: DialogContext['openCreateDiagramDialog'] =
|
||||||
|
useCallback(
|
||||||
|
(props) => {
|
||||||
|
setNewDiagramDialogParams(props);
|
||||||
|
setOpenNewDiagramDialog(true);
|
||||||
|
},
|
||||||
|
[setOpenNewDiagramDialog]
|
||||||
|
);
|
||||||
|
|
||||||
const [openOpenDiagramDialog, setOpenOpenDiagramDialog] = useState(false);
|
const [openOpenDiagramDialog, setOpenOpenDiagramDialog] = useState(false);
|
||||||
const [openDiagramDialogParams, setOpenDiagramDialogParams] =
|
const [openDiagramDialogParams, setOpenDiagramDialogParams] =
|
||||||
useState<Omit<OpenDiagramDialogProps, 'dialog'>>();
|
useState<Omit<OpenDiagramDialogProps, 'dialog'>>();
|
||||||
@@ -128,7 +140,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
return (
|
return (
|
||||||
<dialogContext.Provider
|
<dialogContext.Provider
|
||||||
value={{
|
value={{
|
||||||
openCreateDiagramDialog: () => setOpenNewDiagramDialog(true),
|
openCreateDiagramDialog: openNewDiagramDialogHandler,
|
||||||
closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false),
|
closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false),
|
||||||
openOpenDiagramDialog: openOpenDiagramDialogHandler,
|
openOpenDiagramDialog: openOpenDiagramDialogHandler,
|
||||||
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
|
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
|
||||||
@@ -161,7 +173,10 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<CreateDiagramDialog dialog={{ open: openNewDiagramDialog }} />
|
<CreateDiagramDialog
|
||||||
|
dialog={{ open: openNewDiagramDialog }}
|
||||||
|
{...newDiagramDialogParams}
|
||||||
|
/>
|
||||||
<OpenDiagramDialog
|
<OpenDiagramDialog
|
||||||
dialog={{ open: openOpenDiagramDialog }}
|
dialog={{ open: openOpenDiagramDialog }}
|
||||||
{...openDiagramDialogParams}
|
{...openDiagramDialogParams}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
import type {
|
|
||||||
ChartDBDiff,
|
|
||||||
DiffMap,
|
|
||||||
DiffObject,
|
|
||||||
FieldDiffAttribute,
|
|
||||||
} from '../types';
|
|
||||||
import type { DBField } from '@/lib/domain/db-field';
|
import type { DBField } from '@/lib/domain/db-field';
|
||||||
import type { DBIndex } from '@/lib/domain/db-index';
|
import type { DBIndex } from '@/lib/domain/db-index';
|
||||||
|
import type { ChartDBDiff, DiffMap, DiffObject } from '@/lib/domain/diff/diff';
|
||||||
|
import type { FieldDiffAttribute } from '@/lib/domain/diff/field-diff';
|
||||||
|
|
||||||
export function getDiffMapKey({
|
export function getDiffMapKey({
|
||||||
diffObject,
|
diffObject,
|
||||||
@@ -78,7 +74,7 @@ function compareTables({
|
|||||||
{
|
{
|
||||||
object: 'table',
|
object: 'table',
|
||||||
type: 'added',
|
type: 'added',
|
||||||
tableId: newTable.id,
|
tableAdded: newTable,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
changedTables.set(newTable.id, true);
|
changedTables.set(newTable.id, true);
|
||||||
@@ -100,7 +96,7 @@ function compareTables({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for table name and comments changes
|
// Check for table name, comments and color changes
|
||||||
for (const oldTable of oldTables) {
|
for (const oldTable of oldTables) {
|
||||||
const newTable = newTables.find((t) => t.id === oldTable.id);
|
const newTable = newTables.find((t) => t.id === oldTable.id);
|
||||||
|
|
||||||
@@ -117,7 +113,7 @@ function compareTables({
|
|||||||
object: 'table',
|
object: 'table',
|
||||||
type: 'changed',
|
type: 'changed',
|
||||||
tableId: oldTable.id,
|
tableId: oldTable.id,
|
||||||
attributes: 'name',
|
attribute: 'name',
|
||||||
newValue: newTable.name,
|
newValue: newTable.name,
|
||||||
oldValue: oldTable.name,
|
oldValue: oldTable.name,
|
||||||
}
|
}
|
||||||
@@ -126,7 +122,10 @@ function compareTables({
|
|||||||
changedTables.set(oldTable.id, true);
|
changedTables.set(oldTable.id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldTable.comments !== newTable.comments) {
|
if (
|
||||||
|
(oldTable.comments || newTable.comments) &&
|
||||||
|
oldTable.comments !== newTable.comments
|
||||||
|
) {
|
||||||
diffMap.set(
|
diffMap.set(
|
||||||
getDiffMapKey({
|
getDiffMapKey({
|
||||||
diffObject: 'table',
|
diffObject: 'table',
|
||||||
@@ -137,7 +136,7 @@ function compareTables({
|
|||||||
object: 'table',
|
object: 'table',
|
||||||
type: 'changed',
|
type: 'changed',
|
||||||
tableId: oldTable.id,
|
tableId: oldTable.id,
|
||||||
attributes: 'comments',
|
attribute: 'comments',
|
||||||
newValue: newTable.comments,
|
newValue: newTable.comments,
|
||||||
oldValue: oldTable.comments,
|
oldValue: oldTable.comments,
|
||||||
}
|
}
|
||||||
@@ -145,6 +144,26 @@ function compareTables({
|
|||||||
|
|
||||||
changedTables.set(oldTable.id, true);
|
changedTables.set(oldTable.id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldTable.color !== newTable.color) {
|
||||||
|
diffMap.set(
|
||||||
|
getDiffMapKey({
|
||||||
|
diffObject: 'table',
|
||||||
|
objectId: oldTable.id,
|
||||||
|
attribute: 'color',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
object: 'table',
|
||||||
|
type: 'changed',
|
||||||
|
tableId: oldTable.id,
|
||||||
|
attribute: 'color',
|
||||||
|
newValue: newTable.color,
|
||||||
|
oldValue: oldTable.color,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
changedTables.set(oldTable.id, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +237,7 @@ function compareFields({
|
|||||||
{
|
{
|
||||||
object: 'field',
|
object: 'field',
|
||||||
type: 'added',
|
type: 'added',
|
||||||
fieldId: newField.id,
|
newField,
|
||||||
tableId,
|
tableId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -303,7 +322,10 @@ function compareFieldProperties({
|
|||||||
changedAttributes.push('nullable');
|
changedAttributes.push('nullable');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldField.comments !== newField.comments) {
|
if (
|
||||||
|
(newField.comments || oldField.comments) &&
|
||||||
|
oldField.comments !== newField.comments
|
||||||
|
) {
|
||||||
changedAttributes.push('comments');
|
changedAttributes.push('comments');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,16 +335,16 @@ function compareFieldProperties({
|
|||||||
getDiffMapKey({
|
getDiffMapKey({
|
||||||
diffObject: 'field',
|
diffObject: 'field',
|
||||||
objectId: oldField.id,
|
objectId: oldField.id,
|
||||||
attribute: attribute,
|
attribute,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
object: 'field',
|
object: 'field',
|
||||||
type: 'changed',
|
type: 'changed',
|
||||||
fieldId: oldField.id,
|
fieldId: oldField.id,
|
||||||
tableId,
|
tableId,
|
||||||
attributes: attribute,
|
attribute,
|
||||||
oldValue: oldField[attribute],
|
oldValue: oldField[attribute] ?? '',
|
||||||
newValue: newField[attribute],
|
newValue: newField[attribute] ?? '',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -356,7 +378,7 @@ function compareIndexes({
|
|||||||
{
|
{
|
||||||
object: 'index',
|
object: 'index',
|
||||||
type: 'added',
|
type: 'added',
|
||||||
indexId: newIndex.id,
|
newIndex,
|
||||||
tableId,
|
tableId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -408,7 +430,7 @@ function compareRelationships({
|
|||||||
{
|
{
|
||||||
object: 'relationship',
|
object: 'relationship',
|
||||||
type: 'added',
|
type: 'added',
|
||||||
relationshipId: newRelationship.id,
|
newRelationship,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import type { DiffMap } from './types';
|
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import type { EventEmitter } from 'ahooks/lib/useEventEmitter';
|
import type { EventEmitter } from 'ahooks/lib/useEventEmitter';
|
||||||
import type { DBField } from '@/lib/domain/db-field';
|
import type { DBField } from '@/lib/domain/db-field';
|
||||||
import type { DataType } from '@/lib/data/data-types/data-types';
|
import type { DataType } from '@/lib/data/data-types/data-types';
|
||||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
|
import type { DiffMap } from '@/lib/domain/diff/diff';
|
||||||
|
|
||||||
export type DiffEventType = 'diff_calculated';
|
export type DiffEventType = 'diff_calculated';
|
||||||
|
|
||||||
@@ -14,19 +14,22 @@ export type DiffEventBase<T extends DiffEventType, D> = {
|
|||||||
data: D;
|
data: D;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiffCalculatedEvent = DiffEventBase<
|
export type DiffCalculatedData = {
|
||||||
'diff_calculated',
|
|
||||||
{
|
|
||||||
tablesAdded: DBTable[];
|
tablesAdded: DBTable[];
|
||||||
fieldsAdded: Map<string, DBField[]>;
|
fieldsAdded: Map<string, DBField[]>;
|
||||||
relationshipsAdded: DBRelationship[];
|
relationshipsAdded: DBRelationship[];
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type DiffCalculatedEvent = DiffEventBase<
|
||||||
|
'diff_calculated',
|
||||||
|
DiffCalculatedData
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type DiffEvent = DiffCalculatedEvent;
|
export type DiffEvent = DiffCalculatedEvent;
|
||||||
|
|
||||||
export interface DiffContext {
|
export interface DiffContext {
|
||||||
newDiagram: Diagram | null;
|
newDiagram: Diagram | null;
|
||||||
|
originalDiagram: Diagram | null;
|
||||||
diffMap: DiffMap;
|
diffMap: DiffMap;
|
||||||
hasDiff: boolean;
|
hasDiff: boolean;
|
||||||
|
|
||||||
@@ -43,6 +46,7 @@ export interface DiffContext {
|
|||||||
checkIfNewTable: ({ tableId }: { tableId: string }) => boolean;
|
checkIfNewTable: ({ tableId }: { tableId: string }) => boolean;
|
||||||
checkIfTableRemoved: ({ tableId }: { tableId: string }) => boolean;
|
checkIfTableRemoved: ({ tableId }: { tableId: string }) => boolean;
|
||||||
getTableNewName: ({ tableId }: { tableId: string }) => string | null;
|
getTableNewName: ({ tableId }: { tableId: string }) => string | null;
|
||||||
|
getTableNewColor: ({ tableId }: { tableId: string }) => string | null;
|
||||||
|
|
||||||
// field diff
|
// field diff
|
||||||
checkIfFieldHasChange: ({
|
checkIfFieldHasChange: ({
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import type { DiffContext, DiffEvent } from './diff-context';
|
import type {
|
||||||
|
DiffCalculatedData,
|
||||||
|
DiffContext,
|
||||||
|
DiffEvent,
|
||||||
|
} from './diff-context';
|
||||||
import { diffContext } from './diff-context';
|
import { diffContext } from './diff-context';
|
||||||
import type { ChartDBDiff, DiffMap } from './types';
|
|
||||||
import { generateDiff, getDiffMapKey } from './diff-check/diff-check';
|
import { generateDiff, getDiffMapKey } from './diff-check/diff-check';
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
import { useEventEmitter } from 'ahooks';
|
import { useEventEmitter } from 'ahooks';
|
||||||
import type { DBField } from '@/lib/domain/db-field';
|
import type { DBField } from '@/lib/domain/db-field';
|
||||||
import type { DataType } from '@/lib/data/data-types/data-types';
|
import type { DataType } from '@/lib/data/data-types/data-types';
|
||||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
|
import type { ChartDBDiff, DiffMap } from '@/lib/domain/diff/diff';
|
||||||
|
|
||||||
export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [newDiagram, setNewDiagram] = React.useState<Diagram | null>(null);
|
const [newDiagram, setNewDiagram] = React.useState<Diagram | null>(null);
|
||||||
|
const [originalDiagram, setOriginalDiagram] =
|
||||||
|
React.useState<Diagram | null>(null);
|
||||||
const [diffMap, setDiffMap] = React.useState<DiffMap>(
|
const [diffMap, setDiffMap] = React.useState<DiffMap>(
|
||||||
new Map<string, ChartDBDiff>()
|
new Map<string, ChartDBDiff>()
|
||||||
);
|
);
|
||||||
@@ -39,7 +46,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
if (diff.object === 'field' && diff.type === 'added') {
|
if (diff.object === 'field' && diff.type === 'added') {
|
||||||
const field = newDiagram?.tables
|
const field = newDiagram?.tables
|
||||||
?.find((table) => table.id === diff.tableId)
|
?.find((table) => table.id === diff.tableId)
|
||||||
?.fields.find((f) => f.id === diff.fieldId);
|
?.fields.find((f) => f.id === diff.newField.id);
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
newFieldsMap.set(diff.tableId, [
|
newFieldsMap.set(diff.tableId, [
|
||||||
@@ -67,7 +74,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
diffMap.forEach((diff) => {
|
diffMap.forEach((diff) => {
|
||||||
if (diff.object === 'relationship' && diff.type === 'added') {
|
if (diff.object === 'relationship' && diff.type === 'added') {
|
||||||
const relationship = newDiagram?.relationships?.find(
|
const relationship = newDiagram?.relationships?.find(
|
||||||
(rel) => rel.id === diff.relationshipId
|
(rel) => rel.id === diff.newRelationship.id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (relationship) {
|
if (relationship) {
|
||||||
@@ -81,6 +88,41 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const generateDiffCalculatedData = useCallback(
|
||||||
|
({
|
||||||
|
newDiagram,
|
||||||
|
diffMap,
|
||||||
|
}: {
|
||||||
|
newDiagram: Diagram;
|
||||||
|
diffMap: DiffMap;
|
||||||
|
}): DiffCalculatedData => {
|
||||||
|
return {
|
||||||
|
tablesAdded:
|
||||||
|
newDiagram?.tables?.filter((table) => {
|
||||||
|
const tableKey = getDiffMapKey({
|
||||||
|
diffObject: 'table',
|
||||||
|
objectId: table.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
diffMap.has(tableKey) &&
|
||||||
|
diffMap.get(tableKey)?.type === 'added'
|
||||||
|
);
|
||||||
|
}) ?? [],
|
||||||
|
|
||||||
|
fieldsAdded: generateNewFieldsMap({
|
||||||
|
diffMap: diffMap,
|
||||||
|
newDiagram: newDiagram,
|
||||||
|
}),
|
||||||
|
relationshipsAdded: findNewRelationships({
|
||||||
|
diffMap: diffMap,
|
||||||
|
newDiagram: newDiagram,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[findNewRelationships, generateNewFieldsMap]
|
||||||
|
);
|
||||||
|
|
||||||
const calculateDiff: DiffContext['calculateDiff'] = useCallback(
|
const calculateDiff: DiffContext['calculateDiff'] = useCallback(
|
||||||
({ diagram, newDiagram: newDiagramArg }) => {
|
({ diagram, newDiagram: newDiagramArg }) => {
|
||||||
const {
|
const {
|
||||||
@@ -93,35 +135,17 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setTablesChanged(newChangedTables);
|
setTablesChanged(newChangedTables);
|
||||||
setFieldsChanged(newChangedFields);
|
setFieldsChanged(newChangedFields);
|
||||||
setNewDiagram(newDiagramArg);
|
setNewDiagram(newDiagramArg);
|
||||||
|
setOriginalDiagram(diagram);
|
||||||
|
|
||||||
events.emit({
|
events.emit({
|
||||||
action: 'diff_calculated',
|
action: 'diff_calculated',
|
||||||
data: {
|
data: generateDiffCalculatedData({
|
||||||
tablesAdded:
|
|
||||||
newDiagramArg?.tables?.filter((table) => {
|
|
||||||
const tableKey = getDiffMapKey({
|
|
||||||
diffObject: 'table',
|
|
||||||
objectId: table.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
newDiffs.has(tableKey) &&
|
|
||||||
newDiffs.get(tableKey)?.type === 'added'
|
|
||||||
);
|
|
||||||
}) ?? [],
|
|
||||||
|
|
||||||
fieldsAdded: generateNewFieldsMap({
|
|
||||||
diffMap: newDiffs,
|
diffMap: newDiffs,
|
||||||
newDiagram: newDiagramArg,
|
newDiagram: newDiagramArg,
|
||||||
}),
|
}),
|
||||||
relationshipsAdded: findNewRelationships({
|
|
||||||
diffMap: newDiffs,
|
|
||||||
newDiagram: newDiagramArg,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setDiffMap, events, generateNewFieldsMap, findNewRelationships]
|
[setDiffMap, events, generateDiffCalculatedData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getTableNewName = useCallback<DiffContext['getTableNewName']>(
|
const getTableNewName = useCallback<DiffContext['getTableNewName']>(
|
||||||
@@ -145,6 +169,26 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[diffMap]
|
[diffMap]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getTableNewColor = useCallback<DiffContext['getTableNewColor']>(
|
||||||
|
({ tableId }) => {
|
||||||
|
const tableColorKey = getDiffMapKey({
|
||||||
|
diffObject: 'table',
|
||||||
|
objectId: tableId,
|
||||||
|
attribute: 'color',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (diffMap.has(tableColorKey)) {
|
||||||
|
const diff = diffMap.get(tableColorKey);
|
||||||
|
|
||||||
|
if (diff?.type === 'changed') {
|
||||||
|
return diff.newValue as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[diffMap]
|
||||||
|
);
|
||||||
|
|
||||||
const checkIfTableHasChange = useCallback<
|
const checkIfTableHasChange = useCallback<
|
||||||
DiffContext['checkIfTableHasChange']
|
DiffContext['checkIfTableHasChange']
|
||||||
>(({ tableId }) => tablesChanged.get(tableId) ?? false, [tablesChanged]);
|
>(({ tableId }) => tablesChanged.get(tableId) ?? false, [tablesChanged]);
|
||||||
@@ -296,6 +340,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
<diffContext.Provider
|
<diffContext.Provider
|
||||||
value={{
|
value={{
|
||||||
newDiagram,
|
newDiagram,
|
||||||
|
originalDiagram,
|
||||||
diffMap,
|
diffMap,
|
||||||
hasDiff: diffMap.size > 0,
|
hasDiff: diffMap.size > 0,
|
||||||
|
|
||||||
@@ -306,6 +351,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
checkIfNewTable,
|
checkIfNewTable,
|
||||||
checkIfTableRemoved,
|
checkIfTableRemoved,
|
||||||
checkIfTableHasChange,
|
checkIfTableHasChange,
|
||||||
|
getTableNewColor,
|
||||||
|
|
||||||
// field diff
|
// field diff
|
||||||
checkIfFieldHasChange,
|
checkIfFieldHasChange,
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import type { DataType } from '@/lib/data/data-types/data-types';
|
|
||||||
|
|
||||||
export type TableDiffAttribute = 'name' | 'comments';
|
|
||||||
|
|
||||||
export interface TableDiff {
|
|
||||||
object: 'table';
|
|
||||||
type: 'added' | 'removed' | 'changed';
|
|
||||||
tableId: string;
|
|
||||||
attributes?: TableDiffAttribute;
|
|
||||||
oldValue?: string;
|
|
||||||
newValue?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RelationshipDiff {
|
|
||||||
object: 'relationship';
|
|
||||||
type: 'added' | 'removed';
|
|
||||||
relationshipId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FieldDiffAttribute =
|
|
||||||
| 'name'
|
|
||||||
| 'type'
|
|
||||||
| 'primaryKey'
|
|
||||||
| 'unique'
|
|
||||||
| 'nullable'
|
|
||||||
| 'comments';
|
|
||||||
|
|
||||||
export interface FieldDiff {
|
|
||||||
object: 'field';
|
|
||||||
type: 'added' | 'removed' | 'changed';
|
|
||||||
fieldId: string;
|
|
||||||
tableId: string;
|
|
||||||
attributes?: FieldDiffAttribute;
|
|
||||||
oldValue?: string | boolean | DataType;
|
|
||||||
newValue?: string | boolean | DataType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IndexDiff {
|
|
||||||
object: 'index';
|
|
||||||
type: 'added' | 'removed';
|
|
||||||
indexId: string;
|
|
||||||
tableId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ChartDBDiff = TableDiff | FieldDiff | IndexDiff | RelationshipDiff;
|
|
||||||
|
|
||||||
export type DiffMap = Map<string, ChartDBDiff>;
|
|
||||||
|
|
||||||
export type DiffObject =
|
|
||||||
| TableDiff['object']
|
|
||||||
| FieldDiff['object']
|
|
||||||
| IndexDiff['object']
|
|
||||||
| RelationshipDiff['object'];
|
|
||||||
@@ -3,7 +3,14 @@ import { emptyFn } from '@/lib/utils';
|
|||||||
|
|
||||||
export type ImageType = 'png' | 'jpeg' | 'svg';
|
export type ImageType = 'png' | 'jpeg' | 'svg';
|
||||||
export interface ExportImageContext {
|
export interface ExportImageContext {
|
||||||
exportImage: (type: ImageType, scale: number) => Promise<void>;
|
exportImage: (
|
||||||
|
type: ImageType,
|
||||||
|
options: {
|
||||||
|
includePatternBG: boolean;
|
||||||
|
transparent: boolean;
|
||||||
|
scale: number;
|
||||||
|
}
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const exportImageContext = createContext<ExportImageContext>({
|
export const exportImageContext = createContext<ExportImageContext>({
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
|||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
import logoDark from '@/assets/logo-dark.png';
|
import logoDark from '@/assets/logo-dark.png';
|
||||||
import logoLight from '@/assets/logo-light.png';
|
import logoLight from '@/assets/logo-light.png';
|
||||||
|
import type { EffectiveTheme } from '../theme-context/theme-context';
|
||||||
|
|
||||||
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -57,8 +58,16 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getBackgroundColor = useCallback(
|
||||||
|
(theme: EffectiveTheme, transparent: boolean): string => {
|
||||||
|
if (transparent) return 'transparent';
|
||||||
|
return theme === 'light' ? '#ffffff' : '#141414';
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const exportImage: ExportImageContext['exportImage'] = useCallback(
|
const exportImage: ExportImageContext['exportImage'] = useCallback(
|
||||||
async (type, scale = 1) => {
|
async (type, { includePatternBG, transparent, scale }) => {
|
||||||
showLoader({
|
showLoader({
|
||||||
animated: false,
|
animated: false,
|
||||||
});
|
});
|
||||||
@@ -114,6 +123,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
defs.innerHTML = markerDefs.innerHTML;
|
defs.innerHTML = markerDefs.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (includePatternBG) {
|
||||||
const pattern = document.createElementNS(
|
const pattern = document.createElementNS(
|
||||||
'http://www.w3.org/2000/svg',
|
'http://www.w3.org/2000/svg',
|
||||||
'pattern'
|
'pattern'
|
||||||
@@ -142,6 +152,8 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
|
|
||||||
pattern.appendChild(dot);
|
pattern.appendChild(dot);
|
||||||
defs.appendChild(pattern);
|
defs.appendChild(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
tempSvg.appendChild(defs);
|
tempSvg.appendChild(defs);
|
||||||
|
|
||||||
const backgroundRect = document.createElementNS(
|
const backgroundRect = document.createElementNS(
|
||||||
@@ -196,10 +208,10 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const initialDataUrl = await imageCreateFn(
|
const initialDataUrl = await imageCreateFn(
|
||||||
viewportElement,
|
viewportElement,
|
||||||
{
|
{
|
||||||
backgroundColor:
|
backgroundColor: getBackgroundColor(
|
||||||
effectiveTheme === 'light'
|
effectiveTheme,
|
||||||
? '#ffffff'
|
transparent
|
||||||
: '#141414',
|
),
|
||||||
width: reactFlowBounds.width,
|
width: reactFlowBounds.width,
|
||||||
height: reactFlowBounds.height,
|
height: reactFlowBounds.height,
|
||||||
style: {
|
style: {
|
||||||
@@ -285,6 +297,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
getBackgroundColor,
|
||||||
downloadImage,
|
downloadImage,
|
||||||
getViewport,
|
getViewport,
|
||||||
hideLoader,
|
hideLoader,
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
removeIndex,
|
removeIndex,
|
||||||
updateIndex,
|
updateIndex,
|
||||||
removeRelationships,
|
removeRelationships,
|
||||||
|
addAreas,
|
||||||
|
removeAreas,
|
||||||
|
updateArea,
|
||||||
} = useChartDB();
|
} = useChartDB();
|
||||||
|
|
||||||
const redoActionHandlers = useMemo(
|
const redoActionHandlers = useMemo(
|
||||||
@@ -107,6 +110,15 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
updateHistory: false,
|
updateHistory: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addAreas: ({ redoData: { areas } }) => {
|
||||||
|
return addAreas(areas, { updateHistory: false });
|
||||||
|
},
|
||||||
|
removeAreas: ({ redoData: { areaIds } }) => {
|
||||||
|
return removeAreas(areaIds, { updateHistory: false });
|
||||||
|
},
|
||||||
|
updateArea: ({ redoData: { areaId, area } }) => {
|
||||||
|
return updateArea(areaId, area, { updateHistory: false });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
addTables,
|
addTables,
|
||||||
@@ -126,6 +138,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
addDependencies,
|
addDependencies,
|
||||||
removeDependencies,
|
removeDependencies,
|
||||||
updateDependency,
|
updateDependency,
|
||||||
|
addAreas,
|
||||||
|
removeAreas,
|
||||||
|
updateArea,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -215,6 +230,15 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
updateHistory: false,
|
updateHistory: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addAreas: ({ undoData: { areaIds } }) => {
|
||||||
|
return removeAreas(areaIds, { updateHistory: false });
|
||||||
|
},
|
||||||
|
removeAreas: ({ undoData: { areas } }) => {
|
||||||
|
return addAreas(areas, { updateHistory: false });
|
||||||
|
},
|
||||||
|
updateArea: ({ undoData: { areaId, area } }) => {
|
||||||
|
return updateArea(areaId, area, { updateHistory: false });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
addTables,
|
addTables,
|
||||||
@@ -234,6 +258,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
addDependencies,
|
addDependencies,
|
||||||
removeDependencies,
|
removeDependencies,
|
||||||
updateDependency,
|
updateDependency,
|
||||||
|
addAreas,
|
||||||
|
removeAreas,
|
||||||
|
updateArea,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { DBField } from '@/lib/domain/db-field';
|
|||||||
import type { DBIndex } from '@/lib/domain/db-index';
|
import type { DBIndex } from '@/lib/domain/db-index';
|
||||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
|
import type { Area } from '@/lib/domain/area';
|
||||||
|
|
||||||
type Action = keyof ChartDBContext;
|
type Action = keyof ChartDBContext;
|
||||||
|
|
||||||
@@ -123,6 +124,24 @@ type RedoUndoActionRemoveDependencies = RedoUndoActionBase<
|
|||||||
{ dependencies: DBDependency[] }
|
{ dependencies: DBDependency[] }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type RedoUndoActionAddAreas = RedoUndoActionBase<
|
||||||
|
'addAreas',
|
||||||
|
{ areas: Area[] },
|
||||||
|
{ areaIds: string[] }
|
||||||
|
>;
|
||||||
|
|
||||||
|
type RedoUndoActionUpdateArea = RedoUndoActionBase<
|
||||||
|
'updateArea',
|
||||||
|
{ areaId: string; area: Partial<Area> },
|
||||||
|
{ areaId: string; area: Partial<Area> }
|
||||||
|
>;
|
||||||
|
|
||||||
|
type RedoUndoActionRemoveAreas = RedoUndoActionBase<
|
||||||
|
'removeAreas',
|
||||||
|
{ areaIds: string[] },
|
||||||
|
{ areas: Area[] }
|
||||||
|
>;
|
||||||
|
|
||||||
export type RedoUndoAction =
|
export type RedoUndoAction =
|
||||||
| RedoUndoActionAddTables
|
| RedoUndoActionAddTables
|
||||||
| RedoUndoActionRemoveTables
|
| RedoUndoActionRemoveTables
|
||||||
@@ -140,7 +159,10 @@ export type RedoUndoAction =
|
|||||||
| RedoUndoActionRemoveRelationships
|
| RedoUndoActionRemoveRelationships
|
||||||
| RedoUndoActionAddDependencies
|
| RedoUndoActionAddDependencies
|
||||||
| RedoUndoActionUpdateDependency
|
| RedoUndoActionUpdateDependency
|
||||||
| RedoUndoActionRemoveDependencies;
|
| RedoUndoActionRemoveDependencies
|
||||||
|
| RedoUndoActionAddAreas
|
||||||
|
| RedoUndoActionUpdateArea
|
||||||
|
| RedoUndoActionRemoveAreas;
|
||||||
|
|
||||||
export type RedoActionData<T extends Action> = Extract<
|
export type RedoActionData<T extends Action> = Extract<
|
||||||
RedoUndoAction,
|
RedoUndoAction,
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { emptyFn } from '@/lib/utils';
|
import { emptyFn } from '@/lib/utils';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
export type SidebarSection = 'tables' | 'relationships' | 'dependencies';
|
export type SidebarSection =
|
||||||
|
| 'tables'
|
||||||
|
| 'relationships'
|
||||||
|
| 'dependencies'
|
||||||
|
| 'areas';
|
||||||
|
|
||||||
export interface LayoutContext {
|
export interface LayoutContext {
|
||||||
openedTableInSidebar: string | undefined;
|
openedTableInSidebar: string | undefined;
|
||||||
@@ -16,6 +20,10 @@ export interface LayoutContext {
|
|||||||
openDependencyFromSidebar: (dependencyId: string) => void;
|
openDependencyFromSidebar: (dependencyId: string) => void;
|
||||||
closeAllDependenciesInSidebar: () => void;
|
closeAllDependenciesInSidebar: () => void;
|
||||||
|
|
||||||
|
openedAreaInSidebar: string | undefined;
|
||||||
|
openAreaFromSidebar: (areaId: string) => void;
|
||||||
|
closeAllAreasInSidebar: () => void;
|
||||||
|
|
||||||
selectedSidebarSection: SidebarSection;
|
selectedSidebarSection: SidebarSection;
|
||||||
selectSidebarSection: (section: SidebarSection) => void;
|
selectSidebarSection: (section: SidebarSection) => void;
|
||||||
|
|
||||||
@@ -41,6 +49,10 @@ export const layoutContext = createContext<LayoutContext>({
|
|||||||
openDependencyFromSidebar: emptyFn,
|
openDependencyFromSidebar: emptyFn,
|
||||||
closeAllDependenciesInSidebar: emptyFn,
|
closeAllDependenciesInSidebar: emptyFn,
|
||||||
|
|
||||||
|
openedAreaInSidebar: undefined,
|
||||||
|
openAreaFromSidebar: emptyFn,
|
||||||
|
closeAllAreasInSidebar: emptyFn,
|
||||||
|
|
||||||
selectSidebarSection: emptyFn,
|
selectSidebarSection: emptyFn,
|
||||||
openTableFromSidebar: emptyFn,
|
openTableFromSidebar: emptyFn,
|
||||||
closeAllTablesInSidebar: emptyFn,
|
closeAllTablesInSidebar: emptyFn,
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
React.useState<string | undefined>();
|
React.useState<string | undefined>();
|
||||||
const [openedDependencyInSidebar, setOpenedDependencyInSidebar] =
|
const [openedDependencyInSidebar, setOpenedDependencyInSidebar] =
|
||||||
React.useState<string | undefined>();
|
React.useState<string | undefined>();
|
||||||
|
const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState<
|
||||||
|
string | undefined
|
||||||
|
>();
|
||||||
const [selectedSidebarSection, setSelectedSidebarSection] =
|
const [selectedSidebarSection, setSelectedSidebarSection] =
|
||||||
React.useState<SidebarSection>('tables');
|
React.useState<SidebarSection>('tables');
|
||||||
const [isSidePanelShowed, setIsSidePanelShowed] =
|
const [isSidePanelShowed, setIsSidePanelShowed] =
|
||||||
@@ -30,6 +33,9 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
|
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
|
||||||
() => setOpenedDependencyInSidebar('');
|
() => setOpenedDependencyInSidebar('');
|
||||||
|
|
||||||
|
const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] =
|
||||||
|
() => setOpenedAreaInSidebar('');
|
||||||
|
|
||||||
const hideSidePanel: LayoutContext['hideSidePanel'] = () =>
|
const hideSidePanel: LayoutContext['hideSidePanel'] = () =>
|
||||||
setIsSidePanelShowed(false);
|
setIsSidePanelShowed(false);
|
||||||
|
|
||||||
@@ -62,6 +68,14 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setOpenedDependencyInSidebar(dependencyId);
|
setOpenedDependencyInSidebar(dependencyId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = (
|
||||||
|
areaId
|
||||||
|
) => {
|
||||||
|
showSidePanel();
|
||||||
|
setSelectedSidebarSection('areas');
|
||||||
|
setOpenedAreaInSidebar(areaId);
|
||||||
|
};
|
||||||
|
|
||||||
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
|
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
|
||||||
setIsSelectSchemaOpen(true);
|
setIsSelectSchemaOpen(true);
|
||||||
|
|
||||||
@@ -88,6 +102,9 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
openedDependencyInSidebar,
|
openedDependencyInSidebar,
|
||||||
openDependencyFromSidebar,
|
openDependencyFromSidebar,
|
||||||
closeAllDependenciesInSidebar,
|
closeAllDependenciesInSidebar,
|
||||||
|
openedAreaInSidebar,
|
||||||
|
openAreaFromSidebar,
|
||||||
|
closeAllAreasInSidebar,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { DBRelationship } from '@/lib/domain/db-relationship';
|
|||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import type { ChartDBConfig } from '@/lib/domain/config';
|
import type { ChartDBConfig } from '@/lib/domain/config';
|
||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
|
import type { Area } from '@/lib/domain/area';
|
||||||
|
|
||||||
export interface StorageContext {
|
export interface StorageContext {
|
||||||
// Config operations
|
// Config operations
|
||||||
@@ -17,6 +18,7 @@ export interface StorageContext {
|
|||||||
includeTables?: boolean;
|
includeTables?: boolean;
|
||||||
includeRelationships?: boolean;
|
includeRelationships?: boolean;
|
||||||
includeDependencies?: boolean;
|
includeDependencies?: boolean;
|
||||||
|
includeAreas?: boolean;
|
||||||
}) => Promise<Diagram[]>;
|
}) => Promise<Diagram[]>;
|
||||||
getDiagram: (
|
getDiagram: (
|
||||||
id: string,
|
id: string,
|
||||||
@@ -24,6 +26,7 @@ export interface StorageContext {
|
|||||||
includeTables?: boolean;
|
includeTables?: boolean;
|
||||||
includeRelationships?: boolean;
|
includeRelationships?: boolean;
|
||||||
includeDependencies?: boolean;
|
includeDependencies?: boolean;
|
||||||
|
includeAreas?: boolean;
|
||||||
}
|
}
|
||||||
) => Promise<Diagram | undefined>;
|
) => Promise<Diagram | undefined>;
|
||||||
updateDiagram: (params: {
|
updateDiagram: (params: {
|
||||||
@@ -86,6 +89,20 @@ export interface StorageContext {
|
|||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
listDependencies: (diagramId: string) => Promise<DBDependency[]>;
|
listDependencies: (diagramId: string) => Promise<DBDependency[]>;
|
||||||
deleteDiagramDependencies: (diagramId: string) => Promise<void>;
|
deleteDiagramDependencies: (diagramId: string) => Promise<void>;
|
||||||
|
|
||||||
|
// Area operations
|
||||||
|
addArea: (params: { diagramId: string; area: Area }) => Promise<void>;
|
||||||
|
getArea: (params: {
|
||||||
|
diagramId: string;
|
||||||
|
id: string;
|
||||||
|
}) => Promise<Area | undefined>;
|
||||||
|
updateArea: (params: {
|
||||||
|
id: string;
|
||||||
|
attributes: Partial<Area>;
|
||||||
|
}) => Promise<void>;
|
||||||
|
deleteArea: (params: { diagramId: string; id: string }) => Promise<void>;
|
||||||
|
listAreas: (diagramId: string) => Promise<Area[]>;
|
||||||
|
deleteDiagramAreas: (diagramId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storageInitialValue: StorageContext = {
|
export const storageInitialValue: StorageContext = {
|
||||||
@@ -119,6 +136,13 @@ export const storageInitialValue: StorageContext = {
|
|||||||
deleteDependency: emptyFn,
|
deleteDependency: emptyFn,
|
||||||
listDependencies: emptyFn,
|
listDependencies: emptyFn,
|
||||||
deleteDiagramDependencies: emptyFn,
|
deleteDiagramDependencies: emptyFn,
|
||||||
|
|
||||||
|
addArea: emptyFn,
|
||||||
|
getArea: emptyFn,
|
||||||
|
updateArea: emptyFn,
|
||||||
|
deleteArea: emptyFn,
|
||||||
|
listAreas: emptyFn,
|
||||||
|
deleteDiagramAreas: emptyFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const storageContext =
|
export const storageContext =
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { DBRelationship } from '@/lib/domain/db-relationship';
|
|||||||
import { determineCardinalities } from '@/lib/domain/db-relationship';
|
import { determineCardinalities } from '@/lib/domain/db-relationship';
|
||||||
import type { ChartDBConfig } from '@/lib/domain/config';
|
import type { ChartDBConfig } from '@/lib/domain/config';
|
||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
|
import type { Area } from '@/lib/domain/area';
|
||||||
|
|
||||||
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -29,6 +30,10 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
DBDependency & { diagramId: string },
|
DBDependency & { diagramId: string },
|
||||||
'id' // primary key "id" (for the typings only)
|
'id' // primary key "id" (for the typings only)
|
||||||
>;
|
>;
|
||||||
|
areas: EntityTable<
|
||||||
|
Area & { diagramId: string },
|
||||||
|
'id' // primary key "id" (for the typings only)
|
||||||
|
>;
|
||||||
config: EntityTable<
|
config: EntityTable<
|
||||||
ChartDBConfig & { id: number },
|
ChartDBConfig & { id: number },
|
||||||
'id' // primary key "id" (for the typings only)
|
'id' // primary key "id" (for the typings only)
|
||||||
@@ -148,6 +153,19 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
db.version(10).stores({
|
||||||
|
diagrams:
|
||||||
|
'++id, name, databaseType, databaseEdition, createdAt, updatedAt',
|
||||||
|
db_tables:
|
||||||
|
'++id, diagramId, name, schema, x, y, fields, indexes, color, createdAt, width, comment, isView, isMaterializedView, order',
|
||||||
|
db_relationships:
|
||||||
|
'++id, diagramId, name, sourceSchema, sourceTableId, targetSchema, targetTableId, sourceFieldId, targetFieldId, type, createdAt',
|
||||||
|
db_dependencies:
|
||||||
|
'++id, diagramId, schema, tableId, dependentSchema, dependentTableId, createdAt',
|
||||||
|
areas: '++id, diagramId, name, x, y, width, height, color',
|
||||||
|
config: '++id, defaultDiagramId',
|
||||||
|
});
|
||||||
|
|
||||||
db.on('ready', async () => {
|
db.on('ready', async () => {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
|
|
||||||
@@ -209,6 +227,11 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const areas = diagram.areas ?? [];
|
||||||
|
promises.push(
|
||||||
|
...areas.map((area) => addArea({ diagramId: diagram.id, area }))
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,10 +240,12 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
includeTables?: boolean;
|
includeTables?: boolean;
|
||||||
includeRelationships?: boolean;
|
includeRelationships?: boolean;
|
||||||
includeDependencies?: boolean;
|
includeDependencies?: boolean;
|
||||||
|
includeAreas?: boolean;
|
||||||
} = {
|
} = {
|
||||||
includeRelationships: false,
|
includeRelationships: false,
|
||||||
includeTables: false,
|
includeTables: false,
|
||||||
includeDependencies: false,
|
includeDependencies: false,
|
||||||
|
includeAreas: false,
|
||||||
}
|
}
|
||||||
): Promise<Diagram[]> => {
|
): Promise<Diagram[]> => {
|
||||||
let diagrams = await db.diagrams.toArray();
|
let diagrams = await db.diagrams.toArray();
|
||||||
@@ -252,6 +277,15 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.includeAreas) {
|
||||||
|
diagrams = await Promise.all(
|
||||||
|
diagrams.map(async (diagram) => {
|
||||||
|
diagram.areas = await listAreas(diagram.id);
|
||||||
|
return diagram;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return diagrams;
|
return diagrams;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -261,10 +295,12 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
includeTables?: boolean;
|
includeTables?: boolean;
|
||||||
includeRelationships?: boolean;
|
includeRelationships?: boolean;
|
||||||
includeDependencies?: boolean;
|
includeDependencies?: boolean;
|
||||||
|
includeAreas?: boolean;
|
||||||
} = {
|
} = {
|
||||||
includeRelationships: false,
|
includeRelationships: false,
|
||||||
includeTables: false,
|
includeTables: false,
|
||||||
includeDependencies: false,
|
includeDependencies: false,
|
||||||
|
includeAreas: false,
|
||||||
}
|
}
|
||||||
): Promise<Diagram | undefined> => {
|
): Promise<Diagram | undefined> => {
|
||||||
const diagram = await db.diagrams.get(id);
|
const diagram = await db.diagrams.get(id);
|
||||||
@@ -285,6 +321,10 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
diagram.dependencies = await listDependencies(id);
|
diagram.dependencies = await listDependencies(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.includeAreas) {
|
||||||
|
diagram.areas = await listAreas(id);
|
||||||
|
}
|
||||||
|
|
||||||
return diagram;
|
return diagram;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -323,6 +363,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
db.db_tables.where('diagramId').equals(id).delete(),
|
db.db_tables.where('diagramId').equals(id).delete(),
|
||||||
db.db_relationships.where('diagramId').equals(id).delete(),
|
db.db_relationships.where('diagramId').equals(id).delete(),
|
||||||
db.db_dependencies.where('diagramId').equals(id).delete(),
|
db.db_dependencies.where('diagramId').equals(id).delete(),
|
||||||
|
db.areas.where('diagramId').equals(id).delete(),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -504,6 +545,41 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
.delete();
|
.delete();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addArea: StorageContext['addArea'] = async ({ area, diagramId }) => {
|
||||||
|
await db.areas.add({
|
||||||
|
...area,
|
||||||
|
diagramId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getArea: StorageContext['getArea'] = async ({ diagramId, id }) => {
|
||||||
|
return await db.areas.get({ id, diagramId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateArea: StorageContext['updateArea'] = async ({
|
||||||
|
id,
|
||||||
|
attributes,
|
||||||
|
}) => {
|
||||||
|
await db.areas.update(id, attributes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteArea: StorageContext['deleteArea'] = async ({
|
||||||
|
diagramId,
|
||||||
|
id,
|
||||||
|
}) => {
|
||||||
|
await db.areas.where({ id, diagramId }).delete();
|
||||||
|
};
|
||||||
|
|
||||||
|
const listAreas: StorageContext['listAreas'] = async (diagramId) => {
|
||||||
|
return await db.areas.where('diagramId').equals(diagramId).toArray();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDiagramAreas: StorageContext['deleteDiagramAreas'] = async (
|
||||||
|
diagramId
|
||||||
|
) => {
|
||||||
|
await db.areas.where('diagramId').equals(diagramId).delete();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<storageContext.Provider
|
<storageContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -533,6 +609,12 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
deleteDependency,
|
deleteDependency,
|
||||||
listDependencies,
|
listDependencies,
|
||||||
deleteDiagramDependencies,
|
deleteDiagramDependencies,
|
||||||
|
addArea,
|
||||||
|
getArea,
|
||||||
|
updateArea,
|
||||||
|
deleteArea,
|
||||||
|
listAreas,
|
||||||
|
deleteDiagramAreas,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, {
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
import { Button } from '@/components/button/button';
|
import { Button } from '@/components/button/button';
|
||||||
import {
|
import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
@@ -8,32 +14,10 @@ import {
|
|||||||
DialogInternalContent,
|
DialogInternalContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/dialog/dialog';
|
} from '@/components/dialog/dialog';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
import type { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { Editor } from '@/components/code-snippet/code-snippet';
|
||||||
import { databaseSecondaryLogoMap } from '@/lib/databases';
|
|
||||||
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
|
|
||||||
import { Textarea } from '@/components/textarea/textarea';
|
|
||||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
import {
|
|
||||||
databaseEditionToImageMap,
|
|
||||||
databaseEditionToLabelMap,
|
|
||||||
databaseTypeToEditionMap,
|
|
||||||
} from '@/lib/domain/database-edition';
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from '@/components/avatar/avatar';
|
|
||||||
import { SSMSInfo } from './ssms-info/ssms-info';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@/components/tabs/tabs';
|
|
||||||
import type { DatabaseClient } from '@/lib/domain/database-clients';
|
|
||||||
import {
|
|
||||||
databaseClientToLabelMap,
|
|
||||||
databaseTypeToClientsMap,
|
|
||||||
databaseEditionToClientsMap,
|
|
||||||
} from '@/lib/domain/database-clients';
|
|
||||||
import type { ImportMetadataScripts } from '@/lib/data/import-metadata/scripts/scripts';
|
|
||||||
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
|
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
import { Spinner } from '@/components/spinner/spinner';
|
import { Spinner } from '@/components/spinner/spinner';
|
||||||
@@ -41,9 +25,63 @@ import {
|
|||||||
fixMetadataJson,
|
fixMetadataJson,
|
||||||
isStringMetadataJson,
|
isStringMetadataJson,
|
||||||
} from '@/lib/data/import-metadata/utils';
|
} from '@/lib/data/import-metadata/utils';
|
||||||
|
import {
|
||||||
|
ResizableHandle,
|
||||||
|
ResizablePanel,
|
||||||
|
ResizablePanelGroup,
|
||||||
|
} from '@/components/resizable/resizable';
|
||||||
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
|
import type { OnChange } from '@monaco-editor/react';
|
||||||
|
import { useDebounce } from '@/hooks/use-debounce-v2';
|
||||||
|
import { InstructionsSection } from './instructions-section/instructions-section';
|
||||||
|
import { parseSQLError } from '@/lib/data/sql-import';
|
||||||
|
import type { editor } from 'monaco-editor';
|
||||||
|
|
||||||
const errorScriptOutputMessage =
|
const errorScriptOutputMessage =
|
||||||
'Invalid JSON. Please correct it or contact us at chartdb.io@gmail.com for help.';
|
'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.';
|
||||||
|
|
||||||
|
// Helper to detect if content is likely SQL DDL or JSON
|
||||||
|
const detectContentType = (content: string): 'query' | 'ddl' | null => {
|
||||||
|
if (!content || content.trim().length === 0) return null;
|
||||||
|
|
||||||
|
// Common SQL DDL keywords
|
||||||
|
const ddlKeywords = [
|
||||||
|
'CREATE TABLE',
|
||||||
|
'ALTER TABLE',
|
||||||
|
'DROP TABLE',
|
||||||
|
'CREATE INDEX',
|
||||||
|
'CREATE VIEW',
|
||||||
|
'CREATE PROCEDURE',
|
||||||
|
'CREATE FUNCTION',
|
||||||
|
'CREATE SCHEMA',
|
||||||
|
'CREATE DATABASE',
|
||||||
|
];
|
||||||
|
|
||||||
|
const upperContent = content.toUpperCase();
|
||||||
|
|
||||||
|
// Check for SQL DDL patterns
|
||||||
|
const hasDDLKeywords = ddlKeywords.some((keyword) =>
|
||||||
|
upperContent.includes(keyword)
|
||||||
|
);
|
||||||
|
if (hasDDLKeywords) return 'ddl';
|
||||||
|
|
||||||
|
// Check if it looks like JSON
|
||||||
|
try {
|
||||||
|
// Just check structure, don't need full parse for detection
|
||||||
|
if (
|
||||||
|
(content.trim().startsWith('{') && content.trim().endsWith('}')) ||
|
||||||
|
(content.trim().startsWith('[') && content.trim().endsWith(']'))
|
||||||
|
) {
|
||||||
|
return 'query';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Not valid JSON, might be partial
|
||||||
|
console.error('Error detecting content type:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't confidently detect, return null
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export interface ImportDatabaseProps {
|
export interface ImportDatabaseProps {
|
||||||
goBack?: () => void;
|
goBack?: () => void;
|
||||||
@@ -58,6 +96,8 @@ export interface ImportDatabaseProps {
|
|||||||
>;
|
>;
|
||||||
keepDialogAfterImport?: boolean;
|
keepDialogAfterImport?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
|
importMethod: 'query' | 'ddl';
|
||||||
|
setImportMethod: (method: 'query' | 'ddl') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
||||||
@@ -71,42 +111,52 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
setDatabaseEdition,
|
setDatabaseEdition,
|
||||||
keepDialogAfterImport,
|
keepDialogAfterImport,
|
||||||
title,
|
title,
|
||||||
|
importMethod,
|
||||||
|
setImportMethod,
|
||||||
}) => {
|
}) => {
|
||||||
const databaseClients = useMemo(
|
const { effectiveTheme } = useTheme();
|
||||||
() => [
|
|
||||||
...databaseTypeToClientsMap[databaseType],
|
|
||||||
...(databaseEdition
|
|
||||||
? databaseEditionToClientsMap[databaseEdition]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
[databaseType, databaseEdition]
|
|
||||||
);
|
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [databaseClient, setDatabaseClient] = useState<
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||||
DatabaseClient | undefined
|
|
||||||
>();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [importMetadataScripts, setImportMetadataScripts] =
|
|
||||||
useState<ImportMetadataScripts | null>(null);
|
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
const { isSm: isDesktop } = useBreakpoint('sm');
|
const { isSm: isDesktop } = useBreakpoint('sm');
|
||||||
|
|
||||||
const [showCheckJsonButton, setShowCheckJsonButton] = useState(false);
|
const [showCheckJsonButton, setShowCheckJsonButton] = useState(false);
|
||||||
const [isCheckingJson, setIsCheckingJson] = useState(false);
|
const [isCheckingJson, setIsCheckingJson] = useState(false);
|
||||||
|
|
||||||
const [showSSMSInfoDialog, setShowSSMSInfoDialog] = useState(false);
|
const [showSSMSInfoDialog, setShowSSMSInfoDialog] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadScripts = async () => {
|
setScriptResult('');
|
||||||
const { importMetadataScripts } = await import(
|
setErrorMessage('');
|
||||||
'@/lib/data/import-metadata/scripts/scripts'
|
setShowCheckJsonButton(false);
|
||||||
);
|
}, [importMethod, setScriptResult]);
|
||||||
setImportMetadataScripts(importMetadataScripts);
|
|
||||||
};
|
|
||||||
loadScripts();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
// Check if the ddl is valid
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (importMethod !== 'ddl') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scriptResult.trim()) return;
|
||||||
|
|
||||||
|
parseSQLError({
|
||||||
|
sqlContent: scriptResult,
|
||||||
|
sourceDatabaseType: databaseType,
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
setErrorMessage('');
|
||||||
|
} else if (!result.success && result.error) {
|
||||||
|
setErrorMessage(result.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [importMethod, scriptResult, databaseType]);
|
||||||
|
|
||||||
|
// Check if the script result is a valid JSON
|
||||||
|
useEffect(() => {
|
||||||
|
if (importMethod !== 'query') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (scriptResult.trim().length === 0) {
|
if (scriptResult.trim().length === 0) {
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
setShowCheckJsonButton(false);
|
setShowCheckJsonButton(false);
|
||||||
@@ -126,7 +176,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
setErrorMessage(errorScriptOutputMessage);
|
setErrorMessage(errorScriptOutputMessage);
|
||||||
setShowCheckJsonButton(false);
|
setShowCheckJsonButton(false);
|
||||||
}
|
}
|
||||||
}, [scriptResult]);
|
}, [scriptResult, importMethod]);
|
||||||
|
|
||||||
const handleImport = useCallback(() => {
|
const handleImport = useCallback(() => {
|
||||||
if (errorMessage.length === 0 && scriptResult.trim().length !== 0) {
|
if (errorMessage.length === 0 && scriptResult.trim().length !== 0) {
|
||||||
@@ -134,19 +184,30 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
}
|
}
|
||||||
}, [errorMessage.length, onImport, scriptResult]);
|
}, [errorMessage.length, onImport, scriptResult]);
|
||||||
|
|
||||||
const handleInputChange = useCallback(
|
const formatEditor = useCallback(() => {
|
||||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
if (editorRef.current) {
|
||||||
const inputValue = e.target.value;
|
setTimeout(() => {
|
||||||
setScriptResult(inputValue);
|
editorRef.current
|
||||||
|
?.getAction('editor.action.formatDocument')
|
||||||
|
?.run();
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleInputChange: OnChange = useCallback(
|
||||||
|
(inputValue) => {
|
||||||
|
setScriptResult(inputValue ?? '');
|
||||||
|
|
||||||
// Automatically open SSMS info when input length is exactly 65535
|
// Automatically open SSMS info when input length is exactly 65535
|
||||||
if (inputValue.length === 65535) {
|
if ((inputValue ?? '').length === 65535) {
|
||||||
setShowSSMSInfoDialog(true);
|
setShowSSMSInfoDialog(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setScriptResult]
|
[setScriptResult]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const debouncedHandleInputChange = useDebounce(handleInputChange, 500);
|
||||||
|
|
||||||
const handleCheckJson = useCallback(async () => {
|
const handleCheckJson = useCallback(async () => {
|
||||||
setIsCheckingJson(true);
|
setIsCheckingJson(true);
|
||||||
|
|
||||||
@@ -155,14 +216,49 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
if (isStringMetadataJson(fixedJson)) {
|
if (isStringMetadataJson(fixedJson)) {
|
||||||
setScriptResult(fixedJson);
|
setScriptResult(fixedJson);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
|
formatEditor();
|
||||||
} else {
|
} else {
|
||||||
setScriptResult(fixedJson);
|
setScriptResult(fixedJson);
|
||||||
setErrorMessage(errorScriptOutputMessage);
|
setErrorMessage(errorScriptOutputMessage);
|
||||||
|
formatEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowCheckJsonButton(false);
|
setShowCheckJsonButton(false);
|
||||||
setIsCheckingJson(false);
|
setIsCheckingJson(false);
|
||||||
}, [scriptResult, setScriptResult]);
|
}, [scriptResult, setScriptResult, formatEditor]);
|
||||||
|
|
||||||
|
const detectAndSetImportMethod = useCallback(() => {
|
||||||
|
const content = editorRef.current?.getValue();
|
||||||
|
if (content && content.trim()) {
|
||||||
|
const detectedType = detectContentType(content);
|
||||||
|
if (detectedType && detectedType !== importMethod) {
|
||||||
|
setImportMethod(detectedType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [setImportMethod, importMethod]);
|
||||||
|
|
||||||
|
const [editorDidMount, setEditorDidMount] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editorRef.current && editorDidMount) {
|
||||||
|
editorRef.current.onDidPaste(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
editorRef.current
|
||||||
|
?.getAction('editor.action.formatDocument')
|
||||||
|
?.run();
|
||||||
|
}, 0);
|
||||||
|
setTimeout(detectAndSetImportMethod, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [detectAndSetImportMethod, editorDidMount]);
|
||||||
|
|
||||||
|
const handleEditorDidMount = useCallback(
|
||||||
|
(editor: editor.IStandaloneCodeEditor) => {
|
||||||
|
editorRef.current = editor;
|
||||||
|
setEditorDidMount(true);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const renderHeader = useCallback(() => {
|
const renderHeader = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
@@ -173,228 +269,131 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
);
|
);
|
||||||
}, [title]);
|
}, [title]);
|
||||||
|
|
||||||
|
const renderInstructions = useCallback(
|
||||||
|
() => (
|
||||||
|
<InstructionsSection
|
||||||
|
databaseType={databaseType}
|
||||||
|
importMethod={importMethod}
|
||||||
|
setDatabaseEdition={setDatabaseEdition}
|
||||||
|
setImportMethod={setImportMethod}
|
||||||
|
databaseEdition={databaseEdition}
|
||||||
|
setShowSSMSInfoDialog={setShowSSMSInfoDialog}
|
||||||
|
showSSMSInfoDialog={showSSMSInfoDialog}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
databaseType,
|
||||||
|
importMethod,
|
||||||
|
setDatabaseEdition,
|
||||||
|
setImportMethod,
|
||||||
|
databaseEdition,
|
||||||
|
setShowSSMSInfoDialog,
|
||||||
|
showSSMSInfoDialog,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderOutputTextArea = useCallback(
|
||||||
|
() => (
|
||||||
|
<div className="flex size-full flex-col gap-1 overflow-hidden rounded-md border p-1">
|
||||||
|
<div className="w-full text-center text-xs text-muted-foreground">
|
||||||
|
{importMethod === 'query'
|
||||||
|
? 'Smart Query Output'
|
||||||
|
: 'SQL Script'}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<Suspense fallback={<Spinner />}>
|
||||||
|
<Editor
|
||||||
|
value={scriptResult}
|
||||||
|
onChange={debouncedHandleInputChange}
|
||||||
|
language={importMethod === 'query' ? 'json' : 'sql'}
|
||||||
|
loading={<Spinner />}
|
||||||
|
onMount={handleEditorDidMount}
|
||||||
|
theme={
|
||||||
|
effectiveTheme === 'dark'
|
||||||
|
? 'dbml-dark'
|
||||||
|
: 'dbml-light'
|
||||||
|
}
|
||||||
|
options={{
|
||||||
|
formatOnPaste: true,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
automaticLayout: true,
|
||||||
|
glyphMargin: false,
|
||||||
|
lineNumbers: 'on',
|
||||||
|
guides: {
|
||||||
|
indentation: false,
|
||||||
|
},
|
||||||
|
folding: true,
|
||||||
|
lineNumbersMinChars: 3,
|
||||||
|
renderValidationDecorations: 'off',
|
||||||
|
lineDecorationsWidth: 0,
|
||||||
|
overviewRulerBorder: false,
|
||||||
|
overviewRulerLanes: 0,
|
||||||
|
hideCursorInOverviewRuler: true,
|
||||||
|
contextmenu: false,
|
||||||
|
|
||||||
|
scrollbar: {
|
||||||
|
vertical: 'hidden',
|
||||||
|
horizontal: 'hidden',
|
||||||
|
alwaysConsumeMouseWheel: false,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className="size-full min-h-40"
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{errorMessage ? (
|
||||||
|
<div className="mt-2 flex shrink-0 items-center gap-2">
|
||||||
|
<p className="text-xs text-red-700">{errorMessage}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
errorMessage,
|
||||||
|
scriptResult,
|
||||||
|
importMethod,
|
||||||
|
effectiveTheme,
|
||||||
|
debouncedHandleInputChange,
|
||||||
|
handleEditorDidMount,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const renderContent = useCallback(() => {
|
const renderContent = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<DialogInternalContent>
|
<DialogInternalContent>
|
||||||
<div className="flex w-full flex-1 flex-col gap-6">
|
{isDesktop ? (
|
||||||
{databaseTypeToEditionMap[databaseType].length > 0 ? (
|
<ResizablePanelGroup
|
||||||
<div className="flex flex-col gap-1 md:flex-row">
|
direction={isDesktop ? 'horizontal' : 'vertical'}
|
||||||
<p className="text-sm leading-6 text-muted-foreground">
|
className="min-h-[500px] md:min-h-fit"
|
||||||
{t(
|
|
||||||
'new_diagram_dialog.import_database.database_edition'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<ToggleGroup
|
|
||||||
type="single"
|
|
||||||
className="ml-1 flex-wrap gap-2"
|
|
||||||
value={
|
|
||||||
!databaseEdition
|
|
||||||
? 'regular'
|
|
||||||
: databaseEdition
|
|
||||||
}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setDatabaseEdition(
|
|
||||||
value === 'regular'
|
|
||||||
? undefined
|
|
||||||
: (value as DatabaseEdition)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ToggleGroupItem
|
<ResizablePanel
|
||||||
value="regular"
|
defaultSize={25}
|
||||||
variant="outline"
|
minSize={25}
|
||||||
className="h-6 gap-1 p-0 px-2 shadow-none"
|
maxSize={99}
|
||||||
|
className="min-h-fit rounded-md bg-gradient-to-b from-slate-50 to-slate-100 p-2 dark:from-slate-900 dark:to-slate-800 md:min-h-fit md:min-w-[350px] md:rounded-l-md md:p-2"
|
||||||
>
|
>
|
||||||
<Avatar className="size-4 rounded-none">
|
{renderInstructions()}
|
||||||
<AvatarImage
|
</ResizablePanel>
|
||||||
src={
|
<ResizableHandle withHandle />
|
||||||
databaseSecondaryLogoMap[
|
<ResizablePanel className="min-h-40 py-2 md:px-2 md:py-0">
|
||||||
databaseType
|
{renderOutputTextArea()}
|
||||||
]
|
</ResizablePanel>
|
||||||
}
|
</ResizablePanelGroup>
|
||||||
alt="Regular"
|
|
||||||
/>
|
|
||||||
<AvatarFallback>Regular</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
Regular
|
|
||||||
</ToggleGroupItem>
|
|
||||||
{databaseTypeToEditionMap[databaseType].map(
|
|
||||||
(edition) => (
|
|
||||||
<ToggleGroupItem
|
|
||||||
value={edition}
|
|
||||||
key={edition}
|
|
||||||
variant="outline"
|
|
||||||
className="h-6 gap-1 p-0 px-2 shadow-none"
|
|
||||||
>
|
|
||||||
<Avatar className="size-4">
|
|
||||||
<AvatarImage
|
|
||||||
src={
|
|
||||||
databaseEditionToImageMap[
|
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
alt={
|
|
||||||
databaseEditionToLabelMap[
|
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AvatarFallback>
|
|
||||||
{
|
|
||||||
databaseEditionToLabelMap[
|
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{databaseEditionToLabelMap[edition]}
|
|
||||||
</ToggleGroupItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</ToggleGroup>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
|
|
||||||
<div>
|
|
||||||
1.{' '}
|
|
||||||
{t('new_diagram_dialog.import_database.step_1')}
|
|
||||||
</div>
|
|
||||||
{databaseType === DatabaseType.SQL_SERVER && (
|
|
||||||
<SSMSInfo
|
|
||||||
open={showSSMSInfoDialog}
|
|
||||||
setOpen={setShowSSMSInfoDialog}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{databaseClients.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
|
<div className="flex flex-col gap-2">
|
||||||
className="h-40 w-full flex-auto"
|
{renderInstructions()}
|
||||||
loading={!importMetadataScripts}
|
{renderOutputTextArea()}
|
||||||
code={
|
|
||||||
importMetadataScripts?.[databaseType]?.({
|
|
||||||
databaseEdition,
|
|
||||||
}) ?? ''
|
|
||||||
}
|
|
||||||
language="sql"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-48 flex-col gap-1">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
2. {t('new_diagram_dialog.import_database.step_2')}
|
|
||||||
</p>
|
|
||||||
<Textarea
|
|
||||||
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
|
|
||||||
placeholder={t(
|
|
||||||
'new_diagram_dialog.import_database.script_results_placeholder'
|
|
||||||
)}
|
)}
|
||||||
value={scriptResult}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
/>
|
|
||||||
{showCheckJsonButton || errorMessage ? (
|
|
||||||
<div className="mt-2 flex items-center gap-2">
|
|
||||||
{showCheckJsonButton ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleCheckJson}
|
|
||||||
disabled={isCheckingJson}
|
|
||||||
>
|
|
||||||
{isCheckingJson ? (
|
|
||||||
<Spinner size="small" />
|
|
||||||
) : (
|
|
||||||
t(
|
|
||||||
'new_diagram_dialog.import_database.check_script_result'
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-red-700">
|
|
||||||
{errorMessage}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogInternalContent>
|
</DialogInternalContent>
|
||||||
);
|
);
|
||||||
}, [
|
}, [renderOutputTextArea, renderInstructions, isDesktop]);
|
||||||
databaseEdition,
|
|
||||||
databaseType,
|
|
||||||
errorMessage,
|
|
||||||
handleInputChange,
|
|
||||||
scriptResult,
|
|
||||||
setDatabaseEdition,
|
|
||||||
databaseClients,
|
|
||||||
databaseClient,
|
|
||||||
importMetadataScripts,
|
|
||||||
t,
|
|
||||||
showCheckJsonButton,
|
|
||||||
isCheckingJson,
|
|
||||||
handleCheckJson,
|
|
||||||
showSSMSInfoDialog,
|
|
||||||
setShowSSMSInfoDialog,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const renderFooter = useCallback(() => {
|
const renderFooter = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<DialogFooter className="mt-4 flex !justify-between gap-2">
|
<DialogFooter className="flex !justify-between gap-2">
|
||||||
<div className="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
|
<div className="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
|
||||||
{goBack && (
|
{goBack && (
|
||||||
<Button
|
<Button
|
||||||
@@ -428,7 +427,22 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
</DialogClose>
|
</DialogClose>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{keepDialogAfterImport ? (
|
{showCheckJsonButton ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="default"
|
||||||
|
onClick={handleCheckJson}
|
||||||
|
disabled={isCheckingJson}
|
||||||
|
>
|
||||||
|
{isCheckingJson ? (
|
||||||
|
<Spinner size="small" />
|
||||||
|
) : (
|
||||||
|
t(
|
||||||
|
'new_diagram_dialog.import_database.check_script_result'
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : keepDialogAfterImport ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="default"
|
variant="default"
|
||||||
@@ -446,7 +460,6 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="default"
|
variant="default"
|
||||||
disabled={
|
disabled={
|
||||||
showCheckJsonButton ||
|
|
||||||
scriptResult.trim().length === 0 ||
|
scriptResult.trim().length === 0 ||
|
||||||
errorMessage.length > 0
|
errorMessage.length > 0
|
||||||
}
|
}
|
||||||
@@ -477,6 +490,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
errorMessage.length,
|
errorMessage.length,
|
||||||
scriptResult,
|
scriptResult,
|
||||||
showCheckJsonButton,
|
showCheckJsonButton,
|
||||||
|
isCheckingJson,
|
||||||
|
handleCheckJson,
|
||||||
goBack,
|
goBack,
|
||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import logo from '@/assets/logo-2.png';
|
||||||
|
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
||||||
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
|
import { databaseSecondaryLogoMap } from '@/lib/databases';
|
||||||
|
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
|
import {
|
||||||
|
databaseEditionToImageMap,
|
||||||
|
databaseEditionToLabelMap,
|
||||||
|
databaseTypeToEditionMap,
|
||||||
|
} from '@/lib/domain/database-edition';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarFallback,
|
||||||
|
AvatarImage,
|
||||||
|
} from '@/components/avatar/avatar';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Code } from 'lucide-react';
|
||||||
|
import { SmartQueryInstructions } from './instructions/smart-query-instructions';
|
||||||
|
import { DDLInstructions } from './instructions/ddl-instructions';
|
||||||
|
|
||||||
|
const DatabasesWithoutDDLInstructions: DatabaseType[] = [
|
||||||
|
DatabaseType.CLICKHOUSE,
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface InstructionsSectionProps {
|
||||||
|
databaseType: DatabaseType;
|
||||||
|
databaseEdition?: DatabaseEdition;
|
||||||
|
setDatabaseEdition: React.Dispatch<
|
||||||
|
React.SetStateAction<DatabaseEdition | undefined>
|
||||||
|
>;
|
||||||
|
importMethod: 'query' | 'ddl';
|
||||||
|
setImportMethod: (method: 'query' | 'ddl') => void;
|
||||||
|
showSSMSInfoDialog: boolean;
|
||||||
|
setShowSSMSInfoDialog: (show: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
|
||||||
|
databaseType,
|
||||||
|
databaseEdition,
|
||||||
|
setDatabaseEdition,
|
||||||
|
importMethod,
|
||||||
|
setImportMethod,
|
||||||
|
setShowSSMSInfoDialog,
|
||||||
|
showSSMSInfoDialog,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-1 flex-col gap-4">
|
||||||
|
{databaseTypeToEditionMap[databaseType].length > 0 ? (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="text-sm leading-6 text-primary">
|
||||||
|
{t(
|
||||||
|
'new_diagram_dialog.import_database.database_edition'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
className="ml-1 flex-wrap justify-start gap-2"
|
||||||
|
value={!databaseEdition ? 'regular' : databaseEdition}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setDatabaseEdition(
|
||||||
|
value === 'regular'
|
||||||
|
? undefined
|
||||||
|
: (value as DatabaseEdition)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="regular"
|
||||||
|
variant="outline"
|
||||||
|
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
|
||||||
|
>
|
||||||
|
<Avatar className="size-4 rounded-none">
|
||||||
|
<AvatarImage
|
||||||
|
src={databaseSecondaryLogoMap[databaseType]}
|
||||||
|
alt="Regular"
|
||||||
|
/>
|
||||||
|
<AvatarFallback>Regular</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
Regular
|
||||||
|
</ToggleGroupItem>
|
||||||
|
{databaseTypeToEditionMap[databaseType].map(
|
||||||
|
(edition) => (
|
||||||
|
<ToggleGroupItem
|
||||||
|
value={edition}
|
||||||
|
key={edition}
|
||||||
|
variant="outline"
|
||||||
|
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
|
||||||
|
>
|
||||||
|
<Avatar className="size-4">
|
||||||
|
<AvatarImage
|
||||||
|
src={
|
||||||
|
databaseEditionToImageMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
alt={
|
||||||
|
databaseEditionToLabelMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AvatarFallback>
|
||||||
|
{databaseEditionToLabelMap[edition]}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{databaseEditionToLabelMap[edition]}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{DatabasesWithoutDDLInstructions.includes(databaseType) ? null : (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="text-sm leading-6 text-primary">
|
||||||
|
How would you like to import?
|
||||||
|
</p>
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
className="ml-1 flex-wrap justify-start gap-2"
|
||||||
|
value={importMethod}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
let selectedImportMethod: 'query' | 'ddl' = 'query';
|
||||||
|
if (value) {
|
||||||
|
selectedImportMethod = value as 'query' | 'ddl';
|
||||||
|
}
|
||||||
|
|
||||||
|
setImportMethod(selectedImportMethod);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="query"
|
||||||
|
variant="outline"
|
||||||
|
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
|
||||||
|
>
|
||||||
|
<Avatar className="h-3 w-4 rounded-none">
|
||||||
|
<AvatarImage src={logo} alt="query" />
|
||||||
|
<AvatarFallback>Query</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
Smart Query
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="ddl"
|
||||||
|
variant="outline"
|
||||||
|
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
|
||||||
|
>
|
||||||
|
<Avatar className="size-4 rounded-none">
|
||||||
|
<Code size={16} />
|
||||||
|
</Avatar>
|
||||||
|
SQL Script
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="text-sm font-semibold">Instructions:</div>
|
||||||
|
{importMethod === 'query' ? (
|
||||||
|
<SmartQueryInstructions
|
||||||
|
databaseType={databaseType}
|
||||||
|
databaseEdition={databaseEdition}
|
||||||
|
showSSMSInfoDialog={showSSMSInfoDialog}
|
||||||
|
setShowSSMSInfoDialog={setShowSSMSInfoDialog}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DDLInstructions
|
||||||
|
databaseType={databaseType}
|
||||||
|
databaseEdition={databaseEdition}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
|
||||||
|
|
||||||
|
export interface DDLInstructionStepProps {
|
||||||
|
index: number;
|
||||||
|
text: string;
|
||||||
|
code?: string;
|
||||||
|
example?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DDLInstructionStep: React.FC<DDLInstructionStepProps> = ({
|
||||||
|
index,
|
||||||
|
text,
|
||||||
|
code,
|
||||||
|
example,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex flex-col gap-1 text-sm text-primary">
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">{index}.</span> {text}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{code ? (
|
||||||
|
<div className="h-[60px]">
|
||||||
|
<CodeSnippet
|
||||||
|
className="h-full"
|
||||||
|
code={code}
|
||||||
|
language={'shell'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{example ? (
|
||||||
|
<>
|
||||||
|
<div className="my-2">Example:</div>
|
||||||
|
<div className="h-[60px]">
|
||||||
|
<CodeSnippet
|
||||||
|
className="h-full"
|
||||||
|
code={example}
|
||||||
|
language={'shell'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
|
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
|
import { DDLInstructionStep } from './ddl-instruction-step';
|
||||||
|
|
||||||
|
interface DDLInstruction {
|
||||||
|
text: string;
|
||||||
|
code?: string;
|
||||||
|
example?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DDLInstructionsMap: Record<DatabaseType, DDLInstruction[]> = {
|
||||||
|
[DatabaseType.GENERIC]: [],
|
||||||
|
[DatabaseType.MYSQL]: [
|
||||||
|
{
|
||||||
|
text: 'Install mysqldump.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Execute the following command in your terminal (prefix with sudo on Linux if needed):',
|
||||||
|
code: `mysqldump -h <host> -u <username>\n-P <port> -p --no-data\n<database_name> > <output_path>`,
|
||||||
|
example: `mysqldump -h localhost -u root -P\n3306 -p --no-data my_db >\nschema_export.sql`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Open the exported SQL file, copy its contents, and paste them here.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[DatabaseType.POSTGRESQL]: [
|
||||||
|
{
|
||||||
|
text: 'Install pg_dump.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Execute the following command in your terminal (prefix with sudo on Linux if needed):',
|
||||||
|
code: `pg_dump -h <host> -p <port> -d <database_name> \n -U <username> -s -F p -E UTF-8 \n -f <output_file_path>`,
|
||||||
|
example: `pg_dump -h localhost -p 5432 -d my_db \n -U postgres -s -F p -E UTF-8 \n -f schema_export.sql`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Open the exported SQL file, copy its contents, and paste them here.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[DatabaseType.SQLITE]: [
|
||||||
|
{
|
||||||
|
text: 'Install sqlite3.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Execute the following command in your terminal:',
|
||||||
|
code: `sqlite3 <database_file_path>\n.dump > <output_file_path>`,
|
||||||
|
example: `sqlite3 my_db.db\n.dump > schema_export.sql`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Open the exported SQL file, copy its contents, and paste them here.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[DatabaseType.SQL_SERVER]: [
|
||||||
|
{
|
||||||
|
text: 'Download and install SQL Server Management Studio (SSMS).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Connect to your SQL Server instance using SSMS.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Right-click on the database you want to export and select Script Database as > CREATE To > New Query Editor Window.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Copy the generated script and paste it here.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[DatabaseType.CLICKHOUSE]: [],
|
||||||
|
[DatabaseType.COCKROACHDB]: [
|
||||||
|
{
|
||||||
|
text: 'Install pg_dump.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Execute the following command in your terminal (prefix with sudo on Linux if needed):',
|
||||||
|
code: `pg_dump -h <host> -p <port> -d <database_name> \n -U <username> -s -F p -E UTF-8 \n -f <output_file_path>`,
|
||||||
|
example: `pg_dump -h localhost -p 5432 -d my_db \n -U postgres -s -F p -E UTF-8 \n -f schema_export.sql`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Open the exported SQL file, copy its contents, and paste them here.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[DatabaseType.MARIADB]: [
|
||||||
|
{
|
||||||
|
text: 'Install mysqldump.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Execute the following command in your terminal (prefix with sudo on Linux if needed):',
|
||||||
|
code: `mysqldump -h <host> -u <username>\n-P <port> -p --no-data\n<database_name> > <output_path>`,
|
||||||
|
example: `mysqldump -h localhost -u root -P\n3306 -p --no-data my_db >\nschema_export.sql`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Open the exported SQL file, copy its contents, and paste them here.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DDLInstructionsProps {
|
||||||
|
databaseType: DatabaseType;
|
||||||
|
databaseEdition?: DatabaseEdition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DDLInstructions: React.FC<DDLInstructionsProps> = ({
|
||||||
|
databaseType,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{DDLInstructionsMap[databaseType].map((instruction, index) => (
|
||||||
|
<DDLInstructionStep
|
||||||
|
key={index}
|
||||||
|
index={index + 1}
|
||||||
|
text={instruction.text}
|
||||||
|
code={instruction.code}
|
||||||
|
example={instruction.example}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
|
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
|
||||||
|
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
|
import { SSMSInfo } from './ssms-info/ssms-info';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from '@/components/tabs/tabs';
|
||||||
|
import type { DatabaseClient } from '@/lib/domain/database-clients';
|
||||||
|
import { minimizeQuery } from '@/lib/data/import-metadata/utils';
|
||||||
|
import {
|
||||||
|
databaseClientToLabelMap,
|
||||||
|
databaseTypeToClientsMap,
|
||||||
|
databaseEditionToClientsMap,
|
||||||
|
} from '@/lib/domain/database-clients';
|
||||||
|
import type { ImportMetadataScripts } from '@/lib/data/import-metadata/scripts/scripts';
|
||||||
|
|
||||||
|
export interface SmartQueryInstructionsProps {
|
||||||
|
databaseType: DatabaseType;
|
||||||
|
databaseEdition?: DatabaseEdition;
|
||||||
|
showSSMSInfoDialog: boolean;
|
||||||
|
setShowSSMSInfoDialog: (show: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SmartQueryInstructions: React.FC<SmartQueryInstructionsProps> = ({
|
||||||
|
databaseType,
|
||||||
|
databaseEdition,
|
||||||
|
showSSMSInfoDialog,
|
||||||
|
setShowSSMSInfoDialog,
|
||||||
|
}) => {
|
||||||
|
const databaseClients = useMemo(
|
||||||
|
() => [
|
||||||
|
...databaseTypeToClientsMap[databaseType],
|
||||||
|
...(databaseEdition
|
||||||
|
? databaseEditionToClientsMap[databaseEdition]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
[databaseType, databaseEdition]
|
||||||
|
);
|
||||||
|
const [databaseClient, setDatabaseClient] = useState<
|
||||||
|
DatabaseClient | undefined
|
||||||
|
>();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [importMetadataScripts, setImportMetadataScripts] =
|
||||||
|
useState<ImportMetadataScripts | null>(null);
|
||||||
|
|
||||||
|
const code = useMemo(
|
||||||
|
() =>
|
||||||
|
(databaseClients.length > 0
|
||||||
|
? importMetadataScripts?.[databaseType]?.({
|
||||||
|
databaseEdition,
|
||||||
|
databaseClient,
|
||||||
|
})
|
||||||
|
: importMetadataScripts?.[databaseType]?.({
|
||||||
|
databaseEdition,
|
||||||
|
})) ?? '',
|
||||||
|
[
|
||||||
|
databaseType,
|
||||||
|
databaseEdition,
|
||||||
|
databaseClients,
|
||||||
|
importMetadataScripts,
|
||||||
|
databaseClient,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadScripts = async () => {
|
||||||
|
const { importMetadataScripts } = await import(
|
||||||
|
'@/lib/data/import-metadata/scripts/scripts'
|
||||||
|
);
|
||||||
|
setImportMetadataScripts(importMetadataScripts);
|
||||||
|
};
|
||||||
|
loadScripts();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex flex-col gap-1 text-sm text-primary">
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">1.</span>{' '}
|
||||||
|
{t('new_diagram_dialog.import_database.step_1')}
|
||||||
|
</div>
|
||||||
|
{databaseType === DatabaseType.SQL_SERVER && (
|
||||||
|
<SSMSInfo
|
||||||
|
open={showSSMSInfoDialog}
|
||||||
|
setOpen={setShowSSMSInfoDialog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{databaseClients.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 md:h-[200px]"
|
||||||
|
loading={!importMetadataScripts}
|
||||||
|
code={minimizeQuery(code)}
|
||||||
|
codeToCopy={code}
|
||||||
|
language={databaseClient ? 'shell' : 'sql'}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
) : (
|
||||||
|
<CodeSnippet
|
||||||
|
className="h-40 w-full flex-auto md:h-[200px]"
|
||||||
|
loading={!importMetadataScripts}
|
||||||
|
code={minimizeQuery(code)}
|
||||||
|
codeToCopy={code}
|
||||||
|
language="sql"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="text-sm text-primary">
|
||||||
|
<span className="font-medium">2.</span>{' '}
|
||||||
|
{t('new_diagram_dialog.import_database.step_2')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -17,6 +17,7 @@ import { CreateDiagramDialogStep } from './create-diagram-dialog-step';
|
|||||||
import { ImportDatabase } from '../common/import-database/import-database';
|
import { ImportDatabase } from '../common/import-database/import-database';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
|
import { sqlImportToDiagram } from '@/lib/data/sql-import';
|
||||||
|
|
||||||
export interface CreateDiagramDialogProps extends BaseDialogProps {}
|
export interface CreateDiagramDialogProps extends BaseDialogProps {}
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { diagramId } = useChartDB();
|
const { diagramId } = useChartDB();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
|
||||||
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
||||||
DatabaseType.GENERIC
|
DatabaseType.GENERIC
|
||||||
);
|
);
|
||||||
@@ -43,6 +45,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDatabaseEdition(undefined);
|
setDatabaseEdition(undefined);
|
||||||
|
setImportMethod('query');
|
||||||
}, [databaseType]);
|
}, [databaseType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -58,15 +61,25 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
setDatabaseType(DatabaseType.GENERIC);
|
setDatabaseType(DatabaseType.GENERIC);
|
||||||
setDatabaseEdition(undefined);
|
setDatabaseEdition(undefined);
|
||||||
setScriptResult('');
|
setScriptResult('');
|
||||||
|
setImportMethod('query');
|
||||||
}, [dialog.open]);
|
}, [dialog.open]);
|
||||||
|
|
||||||
const hasExistingDiagram = (diagramId ?? '').trim().length !== 0;
|
const hasExistingDiagram = (diagramId ?? '').trim().length !== 0;
|
||||||
|
|
||||||
const importNewDiagram = useCallback(async () => {
|
const importNewDiagram = useCallback(async () => {
|
||||||
|
let diagram: Diagram | undefined;
|
||||||
|
|
||||||
|
if (importMethod === 'ddl') {
|
||||||
|
diagram = await sqlImportToDiagram({
|
||||||
|
sqlContent: scriptResult,
|
||||||
|
sourceDatabaseType: databaseType,
|
||||||
|
targetDatabaseType: databaseType,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
const databaseMetadata: DatabaseMetadata =
|
const databaseMetadata: DatabaseMetadata =
|
||||||
loadDatabaseMetadata(scriptResult);
|
loadDatabaseMetadata(scriptResult);
|
||||||
|
|
||||||
const diagram = await loadFromDatabaseMetadata({
|
diagram = await loadFromDatabaseMetadata({
|
||||||
databaseType,
|
databaseType,
|
||||||
databaseMetadata,
|
databaseMetadata,
|
||||||
diagramNumber,
|
diagramNumber,
|
||||||
@@ -75,12 +88,14 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
? undefined
|
? undefined
|
||||||
: databaseEdition,
|
: databaseEdition,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await addDiagram({ diagram });
|
await addDiagram({ diagram });
|
||||||
await updateConfig({ defaultDiagramId: diagram.id });
|
await updateConfig({ config: { defaultDiagramId: diagram.id } });
|
||||||
closeCreateDiagramDialog();
|
closeCreateDiagramDialog();
|
||||||
navigate(`/diagrams/${diagram.id}`);
|
navigate(`/diagrams/${diagram.id}`);
|
||||||
}, [
|
}, [
|
||||||
|
importMethod,
|
||||||
databaseType,
|
databaseType,
|
||||||
addDiagram,
|
addDiagram,
|
||||||
databaseEdition,
|
databaseEdition,
|
||||||
@@ -105,7 +120,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
await addDiagram({ diagram });
|
await addDiagram({ diagram });
|
||||||
await updateConfig({ defaultDiagramId: diagram.id });
|
await updateConfig({ config: { defaultDiagramId: diagram.id } });
|
||||||
closeCreateDiagramDialog();
|
closeCreateDiagramDialog();
|
||||||
navigate(`/diagrams/${diagram.id}`);
|
navigate(`/diagrams/${diagram.id}`);
|
||||||
setTimeout(
|
setTimeout(
|
||||||
@@ -137,7 +152,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
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]"
|
className="flex max-h-dvh w-full flex-col md:max-w-[900px]"
|
||||||
showClose={hasExistingDiagram}
|
showClose={hasExistingDiagram}
|
||||||
>
|
>
|
||||||
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
||||||
@@ -163,6 +178,8 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
}
|
}
|
||||||
setScriptResult={setScriptResult}
|
setScriptResult={setScriptResult}
|
||||||
title={t('new_diagram_dialog.import_database.title')}
|
title={t('new_diagram_dialog.import_database.title')}
|
||||||
|
importMethod={importMethod}
|
||||||
|
setImportMethod={setImportMethod}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -16,11 +16,20 @@ import type { BaseDialogProps } from '../common/base-dialog-props';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { ImageType } from '@/context/export-image-context/export-image-context';
|
import type { ImageType } from '@/context/export-image-context/export-image-context';
|
||||||
import { useExportImage } from '@/hooks/use-export-image';
|
import { useExportImage } from '@/hooks/use-export-image';
|
||||||
|
import { Checkbox } from '@/components/checkbox/checkbox';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from '@/components/accordion/accordion';
|
||||||
|
|
||||||
export interface ExportImageDialogProps extends BaseDialogProps {
|
export interface ExportImageDialogProps extends BaseDialogProps {
|
||||||
format: ImageType;
|
format: ImageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_INCLUDE_PATTERN_BG = true;
|
||||||
|
const DEFAULT_TRANSPARENT = false;
|
||||||
const DEFAULT_SCALE = '2';
|
const DEFAULT_SCALE = '2';
|
||||||
export const ExportImageDialog: React.FC<ExportImageDialogProps> = ({
|
export const ExportImageDialog: React.FC<ExportImageDialogProps> = ({
|
||||||
dialog,
|
dialog,
|
||||||
@@ -28,17 +37,28 @@ export const ExportImageDialog: React.FC<ExportImageDialogProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [scale, setScale] = useState<string>(DEFAULT_SCALE);
|
const [scale, setScale] = useState<string>(DEFAULT_SCALE);
|
||||||
|
const [includePatternBG, setIncludePatternBG] = useState<boolean>(
|
||||||
|
DEFAULT_INCLUDE_PATTERN_BG
|
||||||
|
);
|
||||||
|
const [transparent, setTransparent] =
|
||||||
|
useState<boolean>(DEFAULT_TRANSPARENT);
|
||||||
const { exportImage } = useExportImage();
|
const { exportImage } = useExportImage();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialog.open) return;
|
if (!dialog.open) return;
|
||||||
setScale(DEFAULT_SCALE);
|
setScale(DEFAULT_SCALE);
|
||||||
|
setIncludePatternBG(DEFAULT_INCLUDE_PATTERN_BG);
|
||||||
|
setTransparent(DEFAULT_TRANSPARENT);
|
||||||
}, [dialog.open]);
|
}, [dialog.open]);
|
||||||
const { closeExportImageDialog } = useDialog();
|
const { closeExportImageDialog } = useDialog();
|
||||||
|
|
||||||
const handleExport = useCallback(() => {
|
const handleExport = useCallback(() => {
|
||||||
exportImage(format, Number(scale));
|
exportImage(format, {
|
||||||
}, [exportImage, format, scale]);
|
transparent,
|
||||||
|
includePatternBG,
|
||||||
|
scale: Number(scale),
|
||||||
|
});
|
||||||
|
}, [exportImage, format, includePatternBG, transparent, scale]);
|
||||||
|
|
||||||
const scaleOptions: SelectBoxOption[] = useMemo(
|
const scaleOptions: SelectBoxOption[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -65,16 +85,80 @@ export const ExportImageDialog: React.FC<ExportImageDialogProps> = ({
|
|||||||
{t('export_image_dialog.description')}
|
{t('export_image_dialog.description')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-1">
|
<div className="flex flex-col gap-4 py-1">
|
||||||
<div className="grid w-full items-center gap-4">
|
|
||||||
<SelectBox
|
<SelectBox
|
||||||
options={scaleOptions}
|
options={scaleOptions}
|
||||||
multiple={false}
|
multiple={false}
|
||||||
value={scale}
|
value={scale}
|
||||||
onChange={(value) => setScale(value as string)}
|
onChange={(value) => setScale(value as string)}
|
||||||
/>
|
/>
|
||||||
|
<Accordion type="single" collapsible className="w-full">
|
||||||
|
<AccordionItem value="settings" className="border-0">
|
||||||
|
<AccordionTrigger
|
||||||
|
className="py-1.5"
|
||||||
|
iconPosition="right"
|
||||||
|
>
|
||||||
|
{t('export_image_dialog.advanced_options')}
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="flex flex-col gap-3 py-2">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Checkbox
|
||||||
|
id="pattern-checkbox"
|
||||||
|
className="mt-1 data-[state=checked]:border-pink-600 data-[state=checked]:bg-pink-600 data-[state=checked]:text-white"
|
||||||
|
checked={includePatternBG}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
setIncludePatternBG(
|
||||||
|
value as boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label
|
||||||
|
htmlFor="pattern-checkbox"
|
||||||
|
className="cursor-pointer font-medium"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
'export_image_dialog.pattern'
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
'export_image_dialog.pattern_description'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Checkbox
|
||||||
|
id="transparent-checkbox"
|
||||||
|
className="mt-1 data-[state=checked]:border-pink-600 data-[state=checked]:bg-pink-600 data-[state=checked]:text-white"
|
||||||
|
checked={transparent}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
setTransparent(value as boolean)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label
|
||||||
|
htmlFor="transparent-checkbox"
|
||||||
|
className="cursor-pointer font-medium"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
'export_image_dialog.transparent'
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
'export_image_dialog.transparent_description'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
<DialogFooter className="flex gap-1 md:justify-between">
|
<DialogFooter className="flex gap-1 md:justify-between">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
components={[
|
components={[
|
||||||
<a
|
<a
|
||||||
key={0}
|
key={0}
|
||||||
href="mailto:chartdb.io@gmail.com"
|
href="mailto:support@chartdb.io"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-pink-600 hover:underline"
|
className="text-pink-600 hover:underline"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ImportDatabase } from '../common/import-database/import-database';
|
|||||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||||
import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||||
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||||
@@ -13,6 +14,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
|||||||
import { useReactFlow } from '@xyflow/react';
|
import { useReactFlow } from '@xyflow/react';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
import { useAlert } from '@/context/alert-context/alert-context';
|
import { useAlert } from '@/context/alert-context/alert-context';
|
||||||
|
import { sqlImportToDiagram } from '@/lib/data/sql-import';
|
||||||
|
|
||||||
export interface ImportDatabaseDialogProps extends BaseDialogProps {
|
export interface ImportDatabaseDialogProps extends BaseDialogProps {
|
||||||
databaseType: DatabaseType;
|
databaseType: DatabaseType;
|
||||||
@@ -22,6 +24,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
dialog,
|
dialog,
|
||||||
databaseType,
|
databaseType,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
|
||||||
const { closeImportDatabaseDialog } = useDialog();
|
const { closeImportDatabaseDialog } = useDialog();
|
||||||
const { showAlert } = useAlert();
|
const { showAlert } = useAlert();
|
||||||
const {
|
const {
|
||||||
@@ -54,10 +57,19 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
}, [dialog.open]);
|
}, [dialog.open]);
|
||||||
|
|
||||||
const importDatabase = useCallback(async () => {
|
const importDatabase = useCallback(async () => {
|
||||||
|
let diagram: Diagram | undefined;
|
||||||
|
|
||||||
|
if (importMethod === 'ddl') {
|
||||||
|
diagram = await sqlImportToDiagram({
|
||||||
|
sqlContent: scriptResult,
|
||||||
|
sourceDatabaseType: databaseType,
|
||||||
|
targetDatabaseType: databaseType,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
const databaseMetadata: DatabaseMetadata =
|
const databaseMetadata: DatabaseMetadata =
|
||||||
loadDatabaseMetadata(scriptResult);
|
loadDatabaseMetadata(scriptResult);
|
||||||
|
|
||||||
const diagram = await loadFromDatabaseMetadata({
|
diagram = await loadFromDatabaseMetadata({
|
||||||
databaseType,
|
databaseType,
|
||||||
databaseMetadata,
|
databaseMetadata,
|
||||||
databaseEdition:
|
databaseEdition:
|
||||||
@@ -65,6 +77,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
? undefined
|
? undefined
|
||||||
: databaseEdition,
|
: databaseEdition,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const tableIdsToRemove = tables
|
const tableIdsToRemove = tables
|
||||||
.filter((table) =>
|
.filter((table) =>
|
||||||
@@ -308,6 +321,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
|
|
||||||
closeImportDatabaseDialog();
|
closeImportDatabaseDialog();
|
||||||
}, [
|
}, [
|
||||||
|
importMethod,
|
||||||
databaseEdition,
|
databaseEdition,
|
||||||
currentDatabaseType,
|
currentDatabaseType,
|
||||||
updateDatabaseType,
|
updateDatabaseType,
|
||||||
@@ -337,7 +351,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex max-h-screen w-[90vw] flex-col overflow-y-auto md:overflow-visible xl:min-w-[45vw]"
|
className="flex max-h-screen w-full flex-col md:max-w-[900px]"
|
||||||
showClose
|
showClose
|
||||||
>
|
>
|
||||||
<ImportDatabase
|
<ImportDatabase
|
||||||
@@ -349,6 +363,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
setScriptResult={setScriptResult}
|
setScriptResult={setScriptResult}
|
||||||
keepDialogAfterImport
|
keepDialogAfterImport
|
||||||
title={t('import_database_dialog.title', { diagramName })}
|
title={t('import_database_dialog.title', { diagramName })}
|
||||||
|
importMethod={importMethod}
|
||||||
|
setImportMethod={setImportMethod}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Editor } from '@/components/code-snippet/code-snippet';
|
import { Editor } from '@/components/code-snippet/code-snippet';
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
import { AlertCircle } from 'lucide-react';
|
import { AlertCircle } from 'lucide-react';
|
||||||
import { importDBMLToDiagram } from '@/lib/dbml-import';
|
import { importDBMLToDiagram, sanitizeDBML } from '@/lib/dbml-import';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { Parser } from '@dbml/core';
|
import { Parser } from '@dbml/core';
|
||||||
import { useCanvas } from '@/hooks/use-canvas';
|
import { useCanvas } from '@/hooks/use-canvas';
|
||||||
@@ -189,8 +189,9 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
if (!content.trim()) return;
|
if (!content.trim()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const sanitizedContent = sanitizeDBML(content);
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
parser.parse(content, 'dbml');
|
parser.parse(sanitizedContent, 'dbml');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const parsedError = parseDBMLError(e);
|
const parsedError = parseDBMLError(e);
|
||||||
if (parsedError) {
|
if (parsedError) {
|
||||||
@@ -241,7 +242,9 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
if (!dbmlContent.trim() || errorMessage) return;
|
if (!dbmlContent.trim() || errorMessage) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const importedDiagram = await importDBMLToDiagram(dbmlContent);
|
// Sanitize DBML content before importing
|
||||||
|
const sanitizedContent = sanitizeDBML(dbmlContent);
|
||||||
|
const importedDiagram = await importDBMLToDiagram(sanitizedContent);
|
||||||
const tableIdsToRemove = tables
|
const tableIdsToRemove = tables
|
||||||
.filter((table) =>
|
.filter((table) =>
|
||||||
importedDiagram.tables?.some(
|
importedDiagram.tables?.some(
|
||||||
@@ -330,7 +333,7 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex h-[80vh] max-h-screen flex-col"
|
className="flex h-[80vh] max-h-screen w-full flex-col md:max-w-[900px]"
|
||||||
showClose
|
showClose
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
|||||||
const openDiagram = useCallback(
|
const openDiagram = useCallback(
|
||||||
(diagramId: string) => {
|
(diagramId: string) => {
|
||||||
if (diagramId) {
|
if (diagramId) {
|
||||||
updateConfig({ defaultDiagramId: diagramId });
|
updateConfig({ config: { defaultDiagramId: diagramId } });
|
||||||
navigate(`/diagrams/${diagramId}`);
|
navigate(`/diagrams/${diagramId}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export const ar: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'مساعدة',
|
help: 'مساعدة',
|
||||||
docs_website: 'الوثائق',
|
docs_website: 'الوثائق',
|
||||||
visit_website: 'ChartDB قم بزيارة',
|
join_discord: 'انضم إلينا على Discord',
|
||||||
join_discord: 'Discord انضم إلينا على',
|
|
||||||
schedule_a_call: '!تحدث معنا',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -212,6 +210,27 @@ export const ar: LanguageTranslation = {
|
|||||||
description: 'إنشاء اعتماد للبدء',
|
description: 'إنشاء اعتماد للبدء',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -238,7 +257,7 @@ export const ar: LanguageTranslation = {
|
|||||||
title: 'إسترد قاعدة بياناتك',
|
title: 'إسترد قاعدة بياناتك',
|
||||||
database_edition: ':إصدار قاعدة البيانات',
|
database_edition: ':إصدار قاعدة البيانات',
|
||||||
step_1: ':قم بتشغيل هذا البرنامج النصي في قاعدة بياناتك',
|
step_1: ':قم بتشغيل هذا البرنامج النصي في قاعدة بياناتك',
|
||||||
step_2: ':إلصق نتيجة البرنامج النصي هنا',
|
step_2: ':إلصق نتيجة البرنامج النصي هنا →',
|
||||||
script_results_placeholder: '...نتيجة البرنامج النصي هنا',
|
script_results_placeholder: '...نتيجة البرنامج النصي هنا',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS تعليمات',
|
button_text: 'SSMS تعليمات',
|
||||||
@@ -332,6 +351,12 @@ export const ar: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'إلغاء',
|
cancel: 'إلغاء',
|
||||||
export: 'تصدير',
|
export: 'تصدير',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -364,7 +389,7 @@ export const ar: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'حدث خطأ أثناء التصدير',
|
title: 'حدث خطأ أثناء التصدير',
|
||||||
description:
|
description:
|
||||||
'chartdb.io@gmail.com حدث خطأ ما. هل تحتاج إلى مساعدة؟',
|
'support@chartdb.io حدث خطأ ما. هل تحتاج إلى مساعدة؟',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
import_diagram_dialog: {
|
import_diagram_dialog: {
|
||||||
@@ -375,7 +400,7 @@ export const ar: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'حدث خطأ أثناء الاستيراد',
|
title: 'حدث خطأ أثناء الاستيراد',
|
||||||
description:
|
description:
|
||||||
'chartdb.io@gmail.com و المحاولة مرة اخرى. هل تحتاج إلى المساعدة؟ JSON غير صالح. يرجى التحقق من JSON الرسم البياني',
|
'support@chartdb.io و المحاولة مرة اخرى. هل تحتاج إلى المساعدة؟ JSON غير صالح. يرجى التحقق من JSON الرسم البياني',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
@@ -402,6 +427,8 @@ export const ar: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'جدول جديد',
|
new_table: 'جدول جديد',
|
||||||
new_relationship: 'علاقة جديدة',
|
new_relationship: 'علاقة جديدة',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const bn: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'সাহায্য',
|
help: 'সাহায্য',
|
||||||
docs_website: 'ডকুমেন্টেশন',
|
docs_website: 'ডকুমেন্টেশন',
|
||||||
visit_website: 'ChartDB ওয়েবসাইটে যান',
|
|
||||||
join_discord: 'আমাদের Discord-এ যোগ দিন',
|
join_discord: 'আমাদের Discord-এ যোগ দিন',
|
||||||
schedule_a_call: 'আমাদের সাথে কথা বলুন!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -213,6 +211,27 @@ export const bn: LanguageTranslation = {
|
|||||||
description: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।',
|
description: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -239,7 +258,7 @@ export const bn: LanguageTranslation = {
|
|||||||
title: 'আপনার ডাটাবেস আমদানি করুন',
|
title: 'আপনার ডাটাবেস আমদানি করুন',
|
||||||
database_edition: 'ডাটাবেস সংস্করণ:',
|
database_edition: 'ডাটাবেস সংস্করণ:',
|
||||||
step_1: 'আপনার ডাটাবেসে এই স্ক্রিপ্ট চালান:',
|
step_1: 'আপনার ডাটাবেসে এই স্ক্রিপ্ট চালান:',
|
||||||
step_2: 'স্ক্রিপ্টের ফলাফল এখানে পেস্ট করুন:',
|
step_2: 'স্ক্রিপ্টের ফলাফল এখানে পেস্ট করুন →',
|
||||||
script_results_placeholder: 'স্ক্রিপ্টের ফলাফল এখানে...',
|
script_results_placeholder: 'স্ক্রিপ্টের ফলাফল এখানে...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS নির্দেশনা',
|
button_text: 'SSMS নির্দেশনা',
|
||||||
@@ -333,6 +352,12 @@ export const bn: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'বাতিল করুন',
|
cancel: 'বাতিল করুন',
|
||||||
export: 'রপ্তানি করুন',
|
export: 'রপ্তানি করুন',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -367,7 +392,7 @@ export const bn: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'চিত্র রপ্তানিতে ত্রুটি',
|
title: 'চিত্র রপ্তানিতে ত্রুটি',
|
||||||
description:
|
description:
|
||||||
'কিছু ভুল হয়েছে। সাহায্যের প্রয়োজন? chartdb.io@gmail.com-এ যোগাযোগ করুন।',
|
'কিছু ভুল হয়েছে। সাহায্যের প্রয়োজন? support@chartdb.io-এ যোগাযোগ করুন।',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -379,7 +404,7 @@ export const bn: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'চিত্র আমদানিতে ত্রুটি',
|
title: 'চিত্র আমদানিতে ত্রুটি',
|
||||||
description:
|
description:
|
||||||
'ডায়াগ্রাম JSON অবৈধ। অনুগ্রহ করে JSON পরীক্ষা করুন এবং আবার চেষ্টা করুন। সাহায্যের প্রয়োজন? chartdb.io@gmail.com-এ যোগাযোগ করুন।',
|
'ডায়াগ্রাম JSON অবৈধ। অনুগ্রহ করে JSON পরীক্ষা করুন এবং আবার চেষ্টা করুন। সাহায্যের প্রয়োজন? support@chartdb.io-এ যোগাযোগ করুন।',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -406,6 +431,8 @@ export const bn: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'নতুন টেবিল',
|
new_table: 'নতুন টেবিল',
|
||||||
new_relationship: 'নতুন সম্পর্ক',
|
new_relationship: 'নতুন সম্পর্ক',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const de: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Hilfe',
|
help: 'Hilfe',
|
||||||
docs_website: 'Dokumentation',
|
docs_website: 'Dokumentation',
|
||||||
visit_website: 'ChartDB Webseite',
|
|
||||||
join_discord: 'Auf Discord beitreten',
|
join_discord: 'Auf Discord beitreten',
|
||||||
schedule_a_call: 'Gespräch vereinbaren',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -215,6 +213,27 @@ export const de: LanguageTranslation = {
|
|||||||
description: 'Erstellen Sie eine Ansicht, um zu beginnen',
|
description: 'Erstellen Sie eine Ansicht, um zu beginnen',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -241,7 +260,7 @@ export const de: LanguageTranslation = {
|
|||||||
title: 'Datenbank importieren',
|
title: 'Datenbank importieren',
|
||||||
database_edition: 'Datenbank Edition:',
|
database_edition: 'Datenbank Edition:',
|
||||||
step_1: 'Führen Sie dieses Skript in Ihrer Datenbank aus:',
|
step_1: 'Führen Sie dieses Skript in Ihrer Datenbank aus:',
|
||||||
step_2: 'Fügen Sie das Skriptergebnis hier ein:',
|
step_2: 'Fügen Sie das Skriptergebnis hier ein →',
|
||||||
script_results_placeholder: 'Skriptergebnisse hier...',
|
script_results_placeholder: 'Skriptergebnisse hier...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS Anweisungen',
|
button_text: 'SSMS Anweisungen',
|
||||||
@@ -336,6 +355,12 @@ export const de: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
export: 'Exportieren',
|
export: 'Exportieren',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -370,7 +395,7 @@ export const de: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -382,7 +407,7 @@ export const de: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -409,6 +434,8 @@ export const de: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Neue Tabelle',
|
new_table: 'Neue Tabelle',
|
||||||
new_relationship: 'Neue Beziehung',
|
new_relationship: 'Neue Beziehung',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export const en = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Help',
|
help: 'Help',
|
||||||
docs_website: 'Docs',
|
docs_website: 'Docs',
|
||||||
visit_website: 'Visit ChartDB',
|
|
||||||
join_discord: 'Join us on Discord',
|
join_discord: 'Join us on Discord',
|
||||||
schedule_a_call: 'Talk with us!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -208,6 +206,26 @@ export const en = {
|
|||||||
description: 'Create a view to get started',
|
description: 'Create a view to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -234,7 +252,7 @@ export const en = {
|
|||||||
title: 'Import your Database',
|
title: 'Import your Database',
|
||||||
database_edition: 'Database Edition:',
|
database_edition: 'Database Edition:',
|
||||||
step_1: 'Run this script in your database:',
|
step_1: 'Run this script in your database:',
|
||||||
step_2: 'Paste the script result here:',
|
step_2: 'Paste the script result into this modal →',
|
||||||
script_results_placeholder: 'Script results here...',
|
script_results_placeholder: 'Script results here...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS Instructions',
|
button_text: 'SSMS Instructions',
|
||||||
@@ -328,6 +346,11 @@ export const en = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
export: 'Export',
|
export: 'Export',
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -361,7 +384,7 @@ export const en = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -373,7 +396,7 @@ export const en = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -400,6 +423,7 @@ export const en = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'New Table',
|
new_table: 'New Table',
|
||||||
new_relationship: 'New Relationship',
|
new_relationship: 'New Relationship',
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export const es: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Ayuda',
|
help: 'Ayuda',
|
||||||
docs_website: 'Documentación',
|
docs_website: 'Documentación',
|
||||||
visit_website: 'Visitar ChartDB',
|
|
||||||
join_discord: 'Únete a nosotros en Discord',
|
join_discord: 'Únete a nosotros en Discord',
|
||||||
schedule_a_call: '¡Habla con nosotros!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -203,6 +201,27 @@ export const es: LanguageTranslation = {
|
|||||||
description: 'Crea una vista para comenzar',
|
description: 'Crea una vista para comenzar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -229,7 +248,7 @@ export const es: LanguageTranslation = {
|
|||||||
title: 'Importa tu Base de Datos',
|
title: 'Importa tu Base de Datos',
|
||||||
database_edition: 'Edición de Base de Datos:',
|
database_edition: 'Edición de Base de Datos:',
|
||||||
step_1: 'Ejecuta este script en tu base de datos:',
|
step_1: 'Ejecuta este script en tu base de datos:',
|
||||||
step_2: 'Pega el resultado del script aquí:',
|
step_2: 'Pega el resultado del script aquí →',
|
||||||
script_results_placeholder: 'Resultados del script aquí...',
|
script_results_placeholder: 'Resultados del script aquí...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'Instrucciones SSMS',
|
button_text: 'Instrucciones SSMS',
|
||||||
@@ -325,6 +344,12 @@ export const es: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
export: 'Exportar',
|
export: 'Exportar',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -368,7 +393,7 @@ export const es: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -380,7 +405,7 @@ export const es: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -407,6 +432,8 @@ export const es: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Nueva Tabla',
|
new_table: 'Nueva Tabla',
|
||||||
new_relationship: 'Nueva Relación',
|
new_relationship: 'Nueva Relación',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export const fr: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Aide',
|
help: 'Aide',
|
||||||
docs_website: 'Documentation',
|
docs_website: 'Documentation',
|
||||||
visit_website: 'Visitez ChartDB',
|
|
||||||
join_discord: 'Rejoignez-nous sur Discord',
|
join_discord: 'Rejoignez-nous sur Discord',
|
||||||
schedule_a_call: 'Parlez avec nous !',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -201,6 +199,27 @@ export const fr: LanguageTranslation = {
|
|||||||
description: 'Créez une vue pour commencer',
|
description: 'Créez une vue pour commencer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -227,7 +246,7 @@ export const fr: LanguageTranslation = {
|
|||||||
title: 'Importer votre Base de Données',
|
title: 'Importer votre Base de Données',
|
||||||
database_edition: 'Édition de la Base de Données :',
|
database_edition: 'Édition de la Base de Données :',
|
||||||
step_1: 'Exécutez ce script dans votre base de données :',
|
step_1: 'Exécutez ce script dans votre base de données :',
|
||||||
step_2: 'Collez le résultat du script ici :',
|
step_2: 'Collez le résultat du script ici →',
|
||||||
script_results_placeholder: 'Résultats du script ici...',
|
script_results_placeholder: 'Résultats du script ici...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'Instructions SSMS',
|
button_text: 'Instructions SSMS',
|
||||||
@@ -288,6 +307,12 @@ export const fr: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
export: 'Exporter',
|
export: 'Exporter',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
multiple_schemas_alert: {
|
||||||
@@ -365,7 +390,7 @@ export const fr: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: "Erreur lors de l'exportation du diagramme",
|
title: "Erreur lors de l'exportation du diagramme",
|
||||||
description:
|
description:
|
||||||
"Une erreur s'est produite. Besoin d'aide ? chartdb.io@gmail.com",
|
"Une erreur s'est produite. Besoin d'aide ? support@chartdb.io",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
import_diagram_dialog: {
|
import_diagram_dialog: {
|
||||||
@@ -376,7 +401,7 @@ export const fr: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: "Erreur lors de l'exportation du diagramme",
|
title: "Erreur lors de l'exportation du diagramme",
|
||||||
description:
|
description:
|
||||||
"Le diagramme JSON n'est pas valide. Veuillez vérifier le JSON et réessayer. Besoin d'aide ? chartdb.io@gmail.com",
|
"Le diagramme JSON n'est pas valide. Veuillez vérifier le JSON et réessayer. Besoin d'aide ? support@chartdb.io",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
@@ -404,6 +429,8 @@ export const fr: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Nouvelle Table',
|
new_table: 'Nouvelle Table',
|
||||||
new_relationship: 'Nouvelle Relation',
|
new_relationship: 'Nouvelle Relation',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const gu: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'મદદ',
|
help: 'મદદ',
|
||||||
docs_website: 'દસ્તાવેજીકરણ',
|
docs_website: 'દસ્તાવેજીકરણ',
|
||||||
visit_website: 'ChartDB વેબસાઇટ પર જાઓ',
|
|
||||||
join_discord: 'અમારા Discordમાં જોડાઓ',
|
join_discord: 'અમારા Discordમાં જોડાઓ',
|
||||||
schedule_a_call: 'અમારી સાથે વાત કરો!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -214,6 +212,27 @@ export const gu: LanguageTranslation = {
|
|||||||
description: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.',
|
description: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -239,7 +258,7 @@ export const gu: LanguageTranslation = {
|
|||||||
title: 'તમારું ડેટાબેસ આયાત કરો',
|
title: 'તમારું ડેટાબેસ આયાત કરો',
|
||||||
database_edition: 'ડેટાબેસ આવૃત્તિ:',
|
database_edition: 'ડેટાબેસ આવૃત્તિ:',
|
||||||
step_1: 'તમારા ડેટાબેસમાં આ સ્ક્રિપ્ટ ચલાવો:',
|
step_1: 'તમારા ડેટાબેસમાં આ સ્ક્રિપ્ટ ચલાવો:',
|
||||||
step_2: 'સ્ક્રિપ્ટનો પરિણામ અહીં પેસ્ટ કરો:',
|
step_2: 'સ્ક્રિપ્ટનો પરિણામ અહીં પેસ્ટ કરો →',
|
||||||
script_results_placeholder: 'સ્ક્રિપ્ટના પરિણામ અહીં...',
|
script_results_placeholder: 'સ્ક્રિપ્ટના પરિણામ અહીં...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS સૂચનાઓ',
|
button_text: 'SSMS સૂચનાઓ',
|
||||||
@@ -333,6 +352,12 @@ export const gu: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'રદ કરો',
|
cancel: 'રદ કરો',
|
||||||
export: 'નિકાસ કરો',
|
export: 'નિકાસ કરો',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -367,7 +392,7 @@ export const gu: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'ડાયાગ્રામ નિકાસમાં ભૂલ',
|
title: 'ડાયાગ્રામ નિકાસમાં ભૂલ',
|
||||||
description:
|
description:
|
||||||
'કશુક તો ખોટું થયું. મદદ જોઈએ? chartdb.io@gmail.com પર સંપર્ક કરો.',
|
'કશુક તો ખોટું થયું. મદદ જોઈએ? support@chartdb.io પર સંપર્ક કરો.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -379,7 +404,7 @@ export const gu: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'ડાયાગ્રામ આયાતમાં ભૂલ',
|
title: 'ડાયાગ્રામ આયાતમાં ભૂલ',
|
||||||
description:
|
description:
|
||||||
'ડાયાગ્રામ JSON અમાન્ય છે. કૃપા કરીને JSON તપાસો અને ફરી પ્રયાસ કરો. મદદ જોઈએ? chartdb.io@gmail.com પર સંપર્ક કરો.',
|
'ડાયાગ્રામ JSON અમાન્ય છે. કૃપા કરીને JSON તપાસો અને ફરી પ્રયાસ કરો. મદદ જોઈએ? support@chartdb.io પર સંપર્ક કરો.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -406,6 +431,8 @@ export const gu: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'નવું ટેબલ',
|
new_table: 'નવું ટેબલ',
|
||||||
new_relationship: 'નવો સંબંધ',
|
new_relationship: 'નવો સંબંધ',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export const hi: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'मदद',
|
help: 'मदद',
|
||||||
docs_website: 'દસ્તાવેજીકરણ',
|
docs_website: 'દસ્તાવેજીકરણ',
|
||||||
visit_website: 'ChartDB वेबसाइट पर जाएँ',
|
|
||||||
join_discord: 'हमसे Discord पर जुड़ें',
|
join_discord: 'हमसे Discord पर जुड़ें',
|
||||||
schedule_a_call: 'हमसे बात करें!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -214,6 +212,27 @@ export const hi: LanguageTranslation = {
|
|||||||
description: 'इस अनुभाग में कोई निर्भरता उपलब्ध नहीं है।',
|
description: 'इस अनुभाग में कोई निर्भरता उपलब्ध नहीं है।',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -240,7 +259,7 @@ export const hi: LanguageTranslation = {
|
|||||||
title: 'अपना डेटाबेस आयात करें',
|
title: 'अपना डेटाबेस आयात करें',
|
||||||
database_edition: 'डेटाबेस संस्करण:',
|
database_edition: 'डेटाबेस संस्करण:',
|
||||||
step_1: 'अपने डेटाबेस में यह स्क्रिप्ट चलाएँ:',
|
step_1: 'अपने डेटाबेस में यह स्क्रिप्ट चलाएँ:',
|
||||||
step_2: 'यहाँ स्क्रिप्ट का परिणाम पेस्ट करें:',
|
step_2: 'यहाँ स्क्रिप्ट का परिणाम पेस्ट करें →',
|
||||||
script_results_placeholder: 'स्क्रिप्ट के परिणाम यहाँ...',
|
script_results_placeholder: 'स्क्रिप्ट के परिणाम यहाँ...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS निर्देश',
|
button_text: 'SSMS निर्देश',
|
||||||
@@ -336,6 +355,12 @@ export const hi: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'रद्द करें',
|
cancel: 'रद्द करें',
|
||||||
export: 'निर्यात करें',
|
export: 'निर्यात करें',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -370,7 +395,7 @@ export const hi: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -382,7 +407,7 @@ export const hi: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -409,6 +434,8 @@ export const hi: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'नई तालिका',
|
new_table: 'नई तालिका',
|
||||||
new_relationship: 'नया संबंध',
|
new_relationship: 'नया संबंध',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -41,10 +41,8 @@ export const id_ID: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
help: {
|
help: {
|
||||||
help: 'Bantuan',
|
help: 'Bantuan',
|
||||||
docs_website: 'દસ્તાવેજીકરણ',
|
docs_website: 'Dokumentasi',
|
||||||
visit_website: 'Kunjungi ChartDB',
|
|
||||||
join_discord: 'Bergabunglah di Discord kami',
|
join_discord: 'Bergabunglah di Discord kami',
|
||||||
schedule_a_call: 'Berbicara dengan kami!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -212,6 +210,27 @@ export const id_ID: LanguageTranslation = {
|
|||||||
description: 'Buat tampilan untuk memulai',
|
description: 'Buat tampilan untuk memulai',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -238,7 +257,7 @@ export const id_ID: LanguageTranslation = {
|
|||||||
title: 'Impor Database Anda',
|
title: 'Impor Database Anda',
|
||||||
database_edition: 'Edisi Database:',
|
database_edition: 'Edisi Database:',
|
||||||
step_1: 'Jalankan skrip ini di database Anda:',
|
step_1: 'Jalankan skrip ini di database Anda:',
|
||||||
step_2: 'Tempel hasil skrip di sini:',
|
step_2: 'Tempel hasil skrip di sini →',
|
||||||
script_results_placeholder: 'Hasil skrip di sini...',
|
script_results_placeholder: 'Hasil skrip di sini...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'Instruksi SSMS',
|
button_text: 'Instruksi SSMS',
|
||||||
@@ -331,6 +350,12 @@ export const id_ID: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Batal',
|
cancel: 'Batal',
|
||||||
export: 'Ekspor',
|
export: 'Ekspor',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -365,7 +390,7 @@ export const id_ID: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error ekspor diagram',
|
title: 'Error ekspor diagram',
|
||||||
description:
|
description:
|
||||||
'Sesuatu yang salah. Butuh bantuan? chartdb.io@gmail.com',
|
'Sesuatu yang salah. Butuh bantuan? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -377,7 +402,7 @@ export const id_ID: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error impor diagram',
|
title: 'Error impor diagram',
|
||||||
description:
|
description:
|
||||||
'Diagram JSON tidak valid. Silakan cek JSON dan coba lagi. Butuh bantuan? chartdb.io@gmail.com',
|
'Diagram JSON tidak valid. Silakan cek JSON dan coba lagi. Butuh bantuan? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -405,6 +430,8 @@ export const id_ID: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Tabel Baru',
|
new_table: 'Tabel Baru',
|
||||||
new_relationship: 'Hubungan Baru',
|
new_relationship: 'Hubungan Baru',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -44,9 +44,7 @@ export const ja: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'ヘルプ',
|
help: 'ヘルプ',
|
||||||
docs_website: 'ドキュメント',
|
docs_website: 'ドキュメント',
|
||||||
visit_website: 'ChartDBにアクセス',
|
|
||||||
join_discord: 'Discordに参加',
|
join_discord: 'Discordに参加',
|
||||||
schedule_a_call: '話しかけてください!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -218,6 +216,27 @@ export const ja: LanguageTranslation = {
|
|||||||
description: 'Create a view to get started',
|
description: 'Create a view to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -244,7 +263,7 @@ export const ja: LanguageTranslation = {
|
|||||||
title: 'データベースをインポート',
|
title: 'データベースをインポート',
|
||||||
database_edition: 'データベースエディション:',
|
database_edition: 'データベースエディション:',
|
||||||
step_1: 'このスクリプトをデータベースで実行してください:',
|
step_1: 'このスクリプトをデータベースで実行してください:',
|
||||||
step_2: 'ここにスクリプトの結果を貼り付けてください:',
|
step_2: 'ここにスクリプトの結果を貼り付けてください →',
|
||||||
script_results_placeholder: 'ここにスクリプトの結果...',
|
script_results_placeholder: 'ここにスクリプトの結果...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMSの手順',
|
button_text: 'SSMSの手順',
|
||||||
@@ -340,6 +359,12 @@ export const ja: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'キャンセル',
|
cancel: 'キャンセル',
|
||||||
export: 'エクスポート',
|
export: 'エクスポート',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -374,7 +399,7 @@ export const ja: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -386,7 +411,7 @@ export const ja: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -413,6 +438,8 @@ export const ja: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '新しいテーブル',
|
new_table: '新しいテーブル',
|
||||||
new_relationship: '新しいリレーションシップ',
|
new_relationship: '新しいリレーションシップ',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: '도움말',
|
help: '도움말',
|
||||||
docs_website: '선적 서류 비치',
|
docs_website: '선적 서류 비치',
|
||||||
visit_website: 'ChartDB 사이트 방문',
|
|
||||||
join_discord: 'Discord 가입',
|
join_discord: 'Discord 가입',
|
||||||
schedule_a_call: 'Talk with us!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -212,6 +210,27 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
description: '뷰 테이블을 만들어 시작하세요.',
|
description: '뷰 테이블을 만들어 시작하세요.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -238,7 +257,7 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
title: '당신의 데이터베이스를 가져오세요',
|
title: '당신의 데이터베이스를 가져오세요',
|
||||||
database_edition: '데이터베이스 세부 종류:',
|
database_edition: '데이터베이스 세부 종류:',
|
||||||
step_1: '데이터베이스에서 아래의 SQL을 실행해주세요:',
|
step_1: '데이터베이스에서 아래의 SQL을 실행해주세요:',
|
||||||
step_2: '이곳에 결과를 붙여넣어주세요:',
|
step_2: '이곳에 결과를 붙여넣어주세요 →',
|
||||||
script_results_placeholder: '이곳에 스크립트 결과를 입력...',
|
script_results_placeholder: '이곳에 스크립트 결과를 입력...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS을 사용하시는 경우',
|
button_text: 'SSMS을 사용하시는 경우',
|
||||||
@@ -331,6 +350,12 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: '취소',
|
cancel: '취소',
|
||||||
export: '내보내기',
|
export: '내보내기',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -364,7 +389,7 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: '다이어그램 내보내기 오류',
|
title: '다이어그램 내보내기 오류',
|
||||||
description:
|
description:
|
||||||
'무언가 문제가 발생하였습니다. 도움이 필요하신 경우 chartdb.io@gmail.com으로 연락해주세요.',
|
'무언가 문제가 발생하였습니다. 도움이 필요하신 경우 support@chartdb.io으로 연락해주세요.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
import_diagram_dialog: {
|
import_diagram_dialog: {
|
||||||
@@ -375,7 +400,7 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: '다이어그램 가져오기 오류',
|
title: '다이어그램 가져오기 오류',
|
||||||
description:
|
description:
|
||||||
'다이어그램 JSON이 유효하지 않습니다. JSON이 올바른 형식인지 확인해주세요. 도움이 필요하신 경우 chartdb.io@gmail.com으로 연락해주세요.',
|
'다이어그램 JSON이 유효하지 않습니다. JSON이 올바른 형식인지 확인해주세요. 도움이 필요하신 경우 support@chartdb.io으로 연락해주세요.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -402,6 +427,8 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '새 테이블',
|
new_table: '새 테이블',
|
||||||
new_relationship: '새 연관관계',
|
new_relationship: '새 연관관계',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const mr: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'मदत',
|
help: 'मदत',
|
||||||
docs_website: 'दस्तऐवजीकरण',
|
docs_website: 'दस्तऐवजीकरण',
|
||||||
visit_website: 'ChartDB ला भेट द्या',
|
|
||||||
join_discord: 'आमच्या डिस्कॉर्डमध्ये सामील व्हा',
|
join_discord: 'आमच्या डिस्कॉर्डमध्ये सामील व्हा',
|
||||||
schedule_a_call: 'आमच्याशी बोला!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -217,6 +215,27 @@ export const mr: LanguageTranslation = {
|
|||||||
description: 'सुरू करण्यासाठी एक दृश्य तयार करा',
|
description: 'सुरू करण्यासाठी एक दृश्य तयार करा',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -243,7 +262,7 @@ export const mr: LanguageTranslation = {
|
|||||||
title: 'तुमचा डेटाबेस आयात करा',
|
title: 'तुमचा डेटाबेस आयात करा',
|
||||||
database_edition: 'डेटाबेस संस्करण:',
|
database_edition: 'डेटाबेस संस्करण:',
|
||||||
step_1: 'तुमच्या डेटाबेसमध्ये हा स्क्रिप्ट चालवा:',
|
step_1: 'तुमच्या डेटाबेसमध्ये हा स्क्रिप्ट चालवा:',
|
||||||
step_2: 'स्क्रिप्टचा परिणाम येथे पेस्ट करा:',
|
step_2: 'स्क्रिप्टचा परिणाम येथे पेस्ट करा →',
|
||||||
script_results_placeholder: 'स्क्रिप्ट परिणाम येथे...',
|
script_results_placeholder: 'स्क्रिप्ट परिणाम येथे...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS सूचना',
|
button_text: 'SSMS सूचना',
|
||||||
@@ -339,6 +358,12 @@ export const mr: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'रद्द करा',
|
cancel: 'रद्द करा',
|
||||||
export: 'निर्यात करा',
|
export: 'निर्यात करा',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -374,7 +399,7 @@ export const mr: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -387,7 +412,7 @@ export const mr: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -415,6 +440,8 @@ export const mr: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'नवीन टेबल',
|
new_table: 'नवीन टेबल',
|
||||||
new_relationship: 'नवीन रिलेशनशिप',
|
new_relationship: 'नवीन रिलेशनशिप',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const ne: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'मद्दत',
|
help: 'मद्दत',
|
||||||
docs_website: 'कागजात',
|
docs_website: 'कागजात',
|
||||||
visit_website: 'वेबसाइटमा जानुहोस्',
|
|
||||||
join_discord: 'डिस्कोर्डमा सामिल हुनुहोस्',
|
join_discord: 'डिस्कोर्डमा सामिल हुनुहोस्',
|
||||||
schedule_a_call: 'कल अनुसूची गर्नुहोस्',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -214,6 +212,27 @@ export const ne: LanguageTranslation = {
|
|||||||
'डिपेन्डेन्सीहरू देखाउनका लागि एक व्यू बनाउनुहोस्',
|
'डिपेन्डेन्सीहरू देखाउनका लागि एक व्यू बनाउनुहोस्',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -241,7 +260,7 @@ export const ne: LanguageTranslation = {
|
|||||||
title: 'तपाईंको डाटाबेस आयात गर्नुहोस्',
|
title: 'तपाईंको डाटाबेस आयात गर्नुहोस्',
|
||||||
database_edition: 'डाटाबेस संस्करण:',
|
database_edition: 'डाटाबेस संस्करण:',
|
||||||
step_1: 'तपाईंको डाटाबेसमा यो स्क्रिप्ट चलाउनुहोस्:',
|
step_1: 'तपाईंको डाटाबेसमा यो स्क्रिप्ट चलाउनुहोस्:',
|
||||||
step_2: 'यो स्क्रिप्ट परिणाम यहाँ पेस्ट गर्नुहोस्:',
|
step_2: 'यो स्क्रिप्ट परिणाम यहाँ पेस्ट गर्नुहोस् →',
|
||||||
script_results_placeholder: 'स्क्रिप्ट परिणाम यहाँ...',
|
script_results_placeholder: 'स्क्रिप्ट परिणाम यहाँ...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS निर्देशन',
|
button_text: 'SSMS निर्देशन',
|
||||||
@@ -336,6 +355,12 @@ export const ne: LanguageTranslation = {
|
|||||||
scale_4x: '४x',
|
scale_4x: '४x',
|
||||||
cancel: 'रद्द गर्नुहोस्',
|
cancel: 'रद्द गर्नुहोस्',
|
||||||
export: 'निर्यात गर्नुहोस्',
|
export: 'निर्यात गर्नुहोस्',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -369,7 +394,7 @@ export const ne: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -381,7 +406,7 @@ export const ne: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'डायाग्राम आयात गर्दा समस्या आयो',
|
title: 'डायाग्राम आयात गर्दा समस्या आयो',
|
||||||
description:
|
description:
|
||||||
'डायाग्राम JSON अमान्य छ। कृपया JSON जाँच गर्नुहोस् र पुन: प्रयास गर्नुहोस्। मद्दत चाहिन्छ? chartdb.io@gmail.com मा सम्पर्क गर्नुहोस्',
|
'डायाग्राम JSON अमान्य छ। कृपया JSON जाँच गर्नुहोस् र पुन: प्रयास गर्नुहोस्। मद्दत चाहिन्छ? support@chartdb.io मा सम्पर्क गर्नुहोस्',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -409,6 +434,8 @@ export const ne: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'नयाँ तालिका',
|
new_table: 'नयाँ तालिका',
|
||||||
new_relationship: 'नयाँ सम्बन्ध',
|
new_relationship: 'नयाँ सम्बन्ध',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Ajuda',
|
help: 'Ajuda',
|
||||||
docs_website: 'Documentação',
|
docs_website: 'Documentação',
|
||||||
visit_website: 'Visitar ChartDB',
|
|
||||||
join_discord: 'Junte-se a nós no Discord',
|
join_discord: 'Junte-se a nós no Discord',
|
||||||
schedule_a_call: 'Fale Conosco!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -213,6 +211,27 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
description: 'Crie uma visualização para começar',
|
description: 'Crie uma visualização para começar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -239,7 +258,7 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
title: 'Importe seu Banco de Dados',
|
title: 'Importe seu Banco de Dados',
|
||||||
database_edition: 'Edição do Banco de Dados:',
|
database_edition: 'Edição do Banco de Dados:',
|
||||||
step_1: 'Execute este script no seu banco de dados:',
|
step_1: 'Execute este script no seu banco de dados:',
|
||||||
step_2: 'Cole o resultado do script aqui:',
|
step_2: 'Cole o resultado do script aqui →',
|
||||||
script_results_placeholder: 'Resultados do script aqui...',
|
script_results_placeholder: 'Resultados do script aqui...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'Instruções do SSMS',
|
button_text: 'Instruções do SSMS',
|
||||||
@@ -334,6 +353,12 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
export: 'Exportar',
|
export: 'Exportar',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -368,7 +393,7 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -380,7 +405,7 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -407,6 +432,8 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Nova Tabela',
|
new_table: 'Nova Tabela',
|
||||||
new_relationship: 'Novo Relacionamento',
|
new_relationship: 'Novo Relacionamento',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -24,28 +24,24 @@ export const ru: LanguageTranslation = {
|
|||||||
view: 'Вид',
|
view: 'Вид',
|
||||||
show_sidebar: 'Показать боковую панель',
|
show_sidebar: 'Показать боковую панель',
|
||||||
hide_sidebar: 'Скрыть боковую панель',
|
hide_sidebar: 'Скрыть боковую панель',
|
||||||
hide_cardinality: 'Скрыть множественность связи',
|
hide_cardinality: 'Скрыть виды связи',
|
||||||
show_cardinality: 'Показать множественность связи',
|
show_cardinality: 'Показать виды связи',
|
||||||
zoom_on_scroll: 'Увеличение при прокрутке',
|
zoom_on_scroll: 'Увеличение при прокрутке',
|
||||||
theme: 'Тема',
|
theme: 'Тема',
|
||||||
show_dependencies: 'Показать зависимости',
|
show_dependencies: 'Показать зависимости',
|
||||||
hide_dependencies: 'Скрыть зависимости',
|
hide_dependencies: 'Скрыть зависимости',
|
||||||
// TODO: Translate
|
show_minimap: 'Показать мини-карту',
|
||||||
show_minimap: 'Show Mini Map',
|
hide_minimap: 'Скрыть мини-карту',
|
||||||
hide_minimap: 'Hide Mini Map',
|
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
|
||||||
backup: {
|
backup: {
|
||||||
backup: 'Backup',
|
backup: 'Бэкап',
|
||||||
export_diagram: 'Export Diagram',
|
export_diagram: 'Экспорт диаграммы',
|
||||||
restore_diagram: 'Restore Diagram',
|
restore_diagram: 'Восстановить диаграмму',
|
||||||
},
|
},
|
||||||
help: {
|
help: {
|
||||||
help: 'Помощь',
|
help: 'Помощь',
|
||||||
docs_website: 'Документация',
|
docs_website: 'Документация',
|
||||||
visit_website: 'Перейти на сайт ChartDB',
|
|
||||||
join_discord: 'Присоединиться к сообществу в Discord',
|
join_discord: 'Присоединиться к сообществу в Discord',
|
||||||
schedule_a_call: 'Поговорите с нами!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -125,17 +121,17 @@ export const ru: LanguageTranslation = {
|
|||||||
add_table: 'Добавить таблицу',
|
add_table: 'Добавить таблицу',
|
||||||
filter: 'Фильтр',
|
filter: 'Фильтр',
|
||||||
collapse: 'Свернуть все',
|
collapse: 'Свернуть все',
|
||||||
// TODO: Translate
|
clear: 'Очистить фильтр',
|
||||||
clear: 'Clear Filter',
|
|
||||||
no_results: 'No tables found matching your filter.',
|
no_results:
|
||||||
// TODO: Translate
|
'Таблицы не найдены, соответствующие вашему фильтру.',
|
||||||
show_list: 'Show Table List',
|
show_list: 'Переключиться на список таблиц',
|
||||||
show_dbml: 'Show DBML Editor',
|
show_dbml: 'Переключиться на редактор DBML',
|
||||||
|
|
||||||
table: {
|
table: {
|
||||||
fields: 'Поля',
|
fields: 'Поля',
|
||||||
nullable: 'Может содержать NULL?',
|
nullable: 'Может быть NULL?',
|
||||||
primary_key: 'Первичный ключ,',
|
primary_key: 'Первичный ключ',
|
||||||
indexes: 'Индексы',
|
indexes: 'Индексы',
|
||||||
comments: 'Комментарии',
|
comments: 'Комментарии',
|
||||||
no_comments: 'Нет комментария',
|
no_comments: 'Нет комментария',
|
||||||
@@ -151,8 +147,7 @@ export const ru: LanguageTranslation = {
|
|||||||
comments: 'Комментарии',
|
comments: 'Комментарии',
|
||||||
no_comments: 'Нет комментария',
|
no_comments: 'Нет комментария',
|
||||||
delete_field: 'Удалить поле',
|
delete_field: 'Удалить поле',
|
||||||
// TODO: Translate
|
character_length: 'Макс. длина',
|
||||||
character_length: 'Max Length',
|
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Атрибуты индекса',
|
title: 'Атрибуты индекса',
|
||||||
@@ -165,7 +160,7 @@ export const ru: LanguageTranslation = {
|
|||||||
change_schema: 'Изменить схему',
|
change_schema: 'Изменить схему',
|
||||||
add_field: 'Добавить поле',
|
add_field: 'Добавить поле',
|
||||||
add_index: 'Добавить индекс',
|
add_index: 'Добавить индекс',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Создать копию',
|
||||||
delete_table: 'Удалить таблицу',
|
delete_table: 'Удалить таблицу',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -182,7 +177,7 @@ export const ru: LanguageTranslation = {
|
|||||||
relationship: {
|
relationship: {
|
||||||
primary: 'Основная таблица',
|
primary: 'Основная таблица',
|
||||||
foreign: 'Справочная таблица',
|
foreign: 'Справочная таблица',
|
||||||
cardinality: 'Тип множественности связи',
|
cardinality: 'Тип множественной связи',
|
||||||
delete_relationship: 'Удалить',
|
delete_relationship: 'Удалить',
|
||||||
relationship_actions: {
|
relationship_actions: {
|
||||||
title: 'Действия',
|
title: 'Действия',
|
||||||
@@ -212,6 +207,28 @@ export const ru: LanguageTranslation = {
|
|||||||
description: 'Создайте представление, чтобы начать',
|
description: 'Создайте представление, чтобы начать',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Области',
|
||||||
|
add_area: 'Добавить область',
|
||||||
|
filter: 'Фильтр',
|
||||||
|
clear: 'Очистить фильтр',
|
||||||
|
|
||||||
|
no_results:
|
||||||
|
'Области не найдены, соответствующие вашему фильтру.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Действия',
|
||||||
|
edit_name: 'Изменить название',
|
||||||
|
delete_area: 'Удалить область',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'Нет областей',
|
||||||
|
description: 'Создайте область, чтобы начать',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -238,7 +255,7 @@ export const ru: LanguageTranslation = {
|
|||||||
title: 'Импортируйте свою базу данных',
|
title: 'Импортируйте свою базу данных',
|
||||||
database_edition: 'Версия базы данных:',
|
database_edition: 'Версия базы данных:',
|
||||||
step_1: 'Запустите этот скрипт в своей базе данных:',
|
step_1: 'Запустите этот скрипт в своей базе данных:',
|
||||||
step_2: 'Вставьте вывод скрипта сюда:',
|
step_2: 'Вставьте вывод скрипта сюда →',
|
||||||
script_results_placeholder: 'Вывод скрипта здесь...',
|
script_results_placeholder: 'Вывод скрипта здесь...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS Инструкции',
|
button_text: 'SSMS Инструкции',
|
||||||
@@ -333,6 +350,12 @@ export const ru: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Отменить',
|
cancel: 'Отменить',
|
||||||
export: 'Экспортировать',
|
export: 'Экспортировать',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -366,7 +389,7 @@ export const ru: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Ошибка экспортирования диаграммы',
|
title: 'Ошибка экспортирования диаграммы',
|
||||||
description:
|
description:
|
||||||
'Что-то пошло не так. Если вам нужна помощь, напишите нам: chartdb.io@gmail.com',
|
'Что-то пошло не так. Если вам нужна помощь, напишите нам: support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
import_diagram_dialog: {
|
import_diagram_dialog: {
|
||||||
@@ -377,21 +400,22 @@ export const ru: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Ошибка при импорте диаграммы',
|
title: 'Ошибка при импорте диаграммы',
|
||||||
description:
|
description:
|
||||||
'Код JSON диаграммы некорректен. Проверьте, пожалуйста, код и попробуйте снова. Проблема не решается? Напишите нам: chartdb.io@gmail.com',
|
'Код JSON диаграммы некорректен. Проверьте, пожалуйста, код и попробуйте снова. Проблема не решается? Напишите нам: support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
example_title: 'Import Example DBML',
|
example_title: 'Импорт DBML',
|
||||||
title: 'Import DBML',
|
title: 'Импортировать DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Импортировать схему базы данных из DBML формата.',
|
||||||
import: 'Import',
|
import: 'Импортировать',
|
||||||
cancel: 'Cancel',
|
cancel: 'Отмена',
|
||||||
skip_and_empty: 'Skip & Empty',
|
skip_and_empty: 'Продолжить с пустой диаграммой',
|
||||||
show_example: 'Show Example',
|
show_example: 'Использовать эту схему',
|
||||||
|
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Ошибка',
|
||||||
description: 'Failed to parse DBML. Please check the syntax.',
|
description:
|
||||||
|
'Ошибка парсинга DBML. Пожалуйста проверьте синтаксис.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -404,13 +428,14 @@ export const ru: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Создать таблицу',
|
new_table: 'Создать таблицу',
|
||||||
new_relationship: 'Создать отношение',
|
new_relationship: 'Создать отношение',
|
||||||
|
new_area: 'Новая область',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Изменить таблицу',
|
edit_table: 'Изменить таблицу',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Создать копию',
|
||||||
delete_table: 'Удалить таблицу',
|
delete_table: 'Удалить таблицу',
|
||||||
add_relationship: 'Add Relationship', // TODO: Translate
|
add_relationship: 'Добавить связь',
|
||||||
},
|
},
|
||||||
|
|
||||||
copy_to_clipboard: 'Скопировать в буфер обмена',
|
copy_to_clipboard: 'Скопировать в буфер обмена',
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const te: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'సహాయం',
|
help: 'సహాయం',
|
||||||
docs_website: 'డాక్యుమెంటేషన్',
|
docs_website: 'డాక్యుమెంటేషన్',
|
||||||
visit_website: 'ChartDB సందర్శించండి',
|
|
||||||
join_discord: 'డిస్కార్డ్లో మా నుంచి చేరండి',
|
join_discord: 'డిస్కార్డ్లో మా నుంచి చేరండి',
|
||||||
schedule_a_call: 'మాతో మాట్లాడండి!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -214,6 +212,27 @@ export const te: LanguageTranslation = {
|
|||||||
description: 'ప్రారంభించడానికి ఒక వీక్షణ సృష్టించండి',
|
description: 'ప్రారంభించడానికి ఒక వీక్షణ సృష్టించండి',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -240,7 +259,7 @@ export const te: LanguageTranslation = {
|
|||||||
title: 'మీ డేటాబేస్ను దిగుమతి చేసుకోండి',
|
title: 'మీ డేటాబేస్ను దిగుమతి చేసుకోండి',
|
||||||
database_edition: 'డేటాబేస్ ఎడిషన్:',
|
database_edition: 'డేటాబేస్ ఎడిషన్:',
|
||||||
step_1: 'ఈ స్క్రిప్ట్ను మీ డేటాబేస్లో అమలు చేయండి:',
|
step_1: 'ఈ స్క్రిప్ట్ను మీ డేటాబేస్లో అమలు చేయండి:',
|
||||||
step_2: 'స్క్రిప్ట్ ఫలితాన్ని ఇక్కడ పేస్ట్ చేయండి:',
|
step_2: 'స్క్రిప్ట్ ఫలితాన్ని ఇక్కడ పేస్ట్ చేయండి →',
|
||||||
script_results_placeholder: 'స్క్రిప్ట్ ఫలితాలు ఇక్కడ...',
|
script_results_placeholder: 'స్క్రిప్ట్ ఫలితాలు ఇక్కడ...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS సూచనల్ని చూపించు',
|
button_text: 'SSMS సూచనల్ని చూపించు',
|
||||||
@@ -335,6 +354,12 @@ export const te: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'రద్దు',
|
cancel: 'రద్దు',
|
||||||
export: 'ఎగుమతి',
|
export: 'ఎగుమతి',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -370,7 +395,7 @@ export const te: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -383,7 +408,7 @@ export const te: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -411,6 +436,8 @@ export const te: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'కొత్త పట్టిక',
|
new_table: 'కొత్త పట్టిక',
|
||||||
new_relationship: 'కొత్త సంబంధం',
|
new_relationship: 'కొత్త సంబంధం',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const tr: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Yardım',
|
help: 'Yardım',
|
||||||
docs_website: 'Belgeleme',
|
docs_website: 'Belgeleme',
|
||||||
visit_website: "ChartDB'yi Ziyaret Et",
|
|
||||||
join_discord: "Discord'a Katıl",
|
join_discord: "Discord'a Katıl",
|
||||||
schedule_a_call: 'Bize Ulaş!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -213,6 +211,27 @@ export const tr: LanguageTranslation = {
|
|||||||
description: 'Başlamak için bir görünüm oluşturun',
|
description: 'Başlamak için bir görünüm oluşturun',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
toolbar: {
|
toolbar: {
|
||||||
zoom_in: 'Yakınlaştır',
|
zoom_in: 'Yakınlaştır',
|
||||||
@@ -236,7 +255,7 @@ export const tr: LanguageTranslation = {
|
|||||||
title: 'Veritabanını İçe Aktar',
|
title: 'Veritabanını İçe Aktar',
|
||||||
database_edition: 'Veritabanı Sürümü:',
|
database_edition: 'Veritabanı Sürümü:',
|
||||||
step_1: 'Bu komut dosyasını veritabanınızda çalıştırın:',
|
step_1: 'Bu komut dosyasını veritabanınızda çalıştırın:',
|
||||||
step_2: 'Komut dosyası sonucunu buraya yapıştırın:',
|
step_2: 'Komut dosyası sonucunu buraya yapıştırın →',
|
||||||
script_results_placeholder: 'Komut dosyası sonuçları burada...',
|
script_results_placeholder: 'Komut dosyası sonuçları burada...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS Talimatları',
|
button_text: 'SSMS Talimatları',
|
||||||
@@ -327,6 +346,12 @@ export const tr: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'İptal',
|
cancel: 'İptal',
|
||||||
export: 'Dışa Aktar',
|
export: 'Dışa Aktar',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
title: 'Şema Seç',
|
title: 'Şema Seç',
|
||||||
@@ -358,7 +383,7 @@ export const tr: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -370,7 +395,7 @@ export const tr: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: 'Error importing diagram',
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
'The diagram JSON is invalid. Please check the JSON and try again. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -396,6 +421,8 @@ export const tr: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Yeni Tablo',
|
new_table: 'Yeni Tablo',
|
||||||
new_relationship: 'Yeni İlişki',
|
new_relationship: 'Yeni İlişki',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Tabloyu Düzenle',
|
edit_table: 'Tabloyu Düzenle',
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export const uk: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Довідка',
|
help: 'Довідка',
|
||||||
docs_website: 'Документація',
|
docs_website: 'Документація',
|
||||||
visit_website: 'Сайт ChartDB',
|
|
||||||
join_discord: 'Приєднуйтесь до нас в Діскорд',
|
join_discord: 'Приєднуйтесь до нас в Діскорд',
|
||||||
schedule_a_call: 'Забронювати зустріч!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -211,6 +209,27 @@ export const uk: LanguageTranslation = {
|
|||||||
description: 'Створіть подання, щоб почати',
|
description: 'Створіть подання, щоб почати',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -237,7 +256,7 @@ export const uk: LanguageTranslation = {
|
|||||||
title: 'Імпортуйте вашу базу даних',
|
title: 'Імпортуйте вашу базу даних',
|
||||||
database_edition: 'Варіант бази даних:',
|
database_edition: 'Варіант бази даних:',
|
||||||
step_1: 'Запустіть цей сценарій у своїй базі даних:',
|
step_1: 'Запустіть цей сценарій у своїй базі даних:',
|
||||||
step_2: 'Вставте сюди результат сценарію:',
|
step_2: 'Вставте сюди результат сценарію →',
|
||||||
script_results_placeholder: 'Результати сценарію має бути тут…',
|
script_results_placeholder: 'Результати сценарію має бути тут…',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS Інструкції',
|
button_text: 'SSMS Інструкції',
|
||||||
@@ -332,6 +351,12 @@ export const uk: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Скасувати',
|
cancel: 'Скасувати',
|
||||||
export: 'Експортувати',
|
export: 'Експортувати',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -364,7 +389,7 @@ export const uk: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Помилка експорут діаграми',
|
title: 'Помилка експорут діаграми',
|
||||||
description:
|
description:
|
||||||
'Щось пішло не так. Потрібна допомога? chartdb.io@gmail.com',
|
'Щось пішло не так. Потрібна допомога? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
import_diagram_dialog: {
|
import_diagram_dialog: {
|
||||||
@@ -375,7 +400,7 @@ export const uk: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Помилка імпорту діаграми',
|
title: 'Помилка імпорту діаграми',
|
||||||
description:
|
description:
|
||||||
'JSON діаграми є неправильним. Будь ласка, перевірте JSON і спробуйте ще раз. Потрібна допомога? chartdb.io@gmail.com',
|
'JSON діаграми є неправильним. Будь ласка, перевірте JSON і спробуйте ще раз. Потрібна допомога? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -402,6 +427,8 @@ export const uk: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Нова таблиця',
|
new_table: 'Нова таблиця',
|
||||||
new_relationship: 'Новий звʼязок',
|
new_relationship: 'Новий звʼязок',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export const vi: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: 'Trợ giúp',
|
help: 'Trợ giúp',
|
||||||
docs_website: 'Tài liệu',
|
docs_website: 'Tài liệu',
|
||||||
visit_website: 'Truy cập ChartDB',
|
|
||||||
join_discord: 'Tham gia Discord',
|
join_discord: 'Tham gia Discord',
|
||||||
schedule_a_call: 'Trò chuyện cùng chúng tôi!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -212,6 +210,27 @@ export const vi: LanguageTranslation = {
|
|||||||
description: 'Tạo bảng xem phụ thuộc để bắt đầu',
|
description: 'Tạo bảng xem phụ thuộc để bắt đầu',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -238,7 +257,7 @@ export const vi: LanguageTranslation = {
|
|||||||
title: 'Nhập cơ sở dữ liệu của bạn',
|
title: 'Nhập cơ sở dữ liệu của bạn',
|
||||||
database_edition: 'Loại:',
|
database_edition: 'Loại:',
|
||||||
step_1: 'Chạy lệnh này trong cơ sở dữ liệu của bạn:',
|
step_1: 'Chạy lệnh này trong cơ sở dữ liệu của bạn:',
|
||||||
step_2: 'Dán kết quả vào đây:',
|
step_2: 'Dán kết quả vào đây →',
|
||||||
script_results_placeholder: 'Kết quả...',
|
script_results_placeholder: 'Kết quả...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'Hướng dẫn SSMS',
|
button_text: 'Hướng dẫn SSMS',
|
||||||
@@ -331,6 +350,12 @@ export const vi: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: 'Hủy',
|
cancel: 'Hủy',
|
||||||
export: 'Xuất',
|
export: 'Xuất',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -364,7 +389,7 @@ export const vi: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Lỗi khi xuất sơ đồ',
|
title: 'Lỗi khi xuất sơ đồ',
|
||||||
description:
|
description:
|
||||||
'Có gì đó không ổn. Cần trợ giúp? chartdb.io@gmail.com',
|
'Có gì đó không ổn. Cần trợ giúp? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -376,7 +401,7 @@ export const vi: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Lỗi khi nhập sơ đồ',
|
title: 'Lỗi khi nhập sơ đồ',
|
||||||
description:
|
description:
|
||||||
'Sơ đồ ở dạng JSON không hợp lệ. Vui lòng kiểm tra JSON và thử lại. Bạn cần trợ giúp? chartdb.io@gmail.com',
|
'Sơ đồ ở dạng JSON không hợp lệ. Vui lòng kiểm tra JSON và thử lại. Bạn cần trợ giúp? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -403,6 +428,8 @@ export const vi: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Tạo bảng mới',
|
new_table: 'Tạo bảng mới',
|
||||||
new_relationship: 'Tạo quan hệ mới',
|
new_relationship: 'Tạo quan hệ mới',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: '帮助',
|
help: '帮助',
|
||||||
docs_website: '文档',
|
docs_website: '文档',
|
||||||
visit_website: '访问 ChartDB',
|
|
||||||
join_discord: '在 Discord 上加入我们',
|
join_discord: '在 Discord 上加入我们',
|
||||||
schedule_a_call: '和我们交流!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -209,6 +207,27 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
description: '创建视图以开始',
|
description: '创建视图以开始',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -234,7 +253,7 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
title: '导入您的数据库',
|
title: '导入您的数据库',
|
||||||
database_edition: '数据库类型:',
|
database_edition: '数据库类型:',
|
||||||
step_1: '在您的数据库中执行以下脚本:',
|
step_1: '在您的数据库中执行以下脚本:',
|
||||||
step_2: '将结果粘贴于此:',
|
step_2: '将结果粘贴于此 →',
|
||||||
script_results_placeholder: '结果...',
|
script_results_placeholder: '结果...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS 说明',
|
button_text: 'SSMS 说明',
|
||||||
@@ -328,6 +347,12 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
export: '导出',
|
export: '导出',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -360,7 +385,7 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -372,7 +397,7 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: '导入关系图时出错',
|
title: '导入关系图时出错',
|
||||||
description:
|
description:
|
||||||
'关系图 JSON 无效,请检查 JSON 后重试。需要帮助? 联系 chartdb.io@gmail.com',
|
'关系图 JSON 无效,请检查 JSON 后重试。需要帮助? 联系 support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -399,6 +424,8 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '新建表',
|
new_table: '新建表',
|
||||||
new_relationship: '新建关系',
|
new_relationship: '新建关系',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
help: {
|
help: {
|
||||||
help: '幫助',
|
help: '幫助',
|
||||||
docs_website: '文件',
|
docs_website: '文件',
|
||||||
visit_website: '訪問 ChartDB 網站',
|
|
||||||
join_discord: '加入 Discord',
|
join_discord: '加入 Discord',
|
||||||
schedule_a_call: '與我們聯絡!',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -209,6 +207,27 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
description: '請建立檢視以開始',
|
description: '請建立檢視以開始',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
areas_section: {
|
||||||
|
areas: 'Areas',
|
||||||
|
add_area: 'Add Area',
|
||||||
|
filter: 'Filter',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
no_results: 'No areas found matching your filter.',
|
||||||
|
|
||||||
|
area: {
|
||||||
|
area_actions: {
|
||||||
|
title: 'Area Actions',
|
||||||
|
edit_name: 'Edit Name',
|
||||||
|
delete_area: 'Delete Area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
empty_state: {
|
||||||
|
title: 'No areas',
|
||||||
|
description: 'Create an area to get started',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
@@ -234,7 +253,7 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
title: '匯入資料庫',
|
title: '匯入資料庫',
|
||||||
database_edition: '資料庫版本:',
|
database_edition: '資料庫版本:',
|
||||||
step_1: '請在資料庫中執行以下腳本:',
|
step_1: '請在資料庫中執行以下腳本:',
|
||||||
step_2: '將腳本結果貼到此處:',
|
step_2: '將腳本結果貼到此處 →',
|
||||||
script_results_placeholder: '在此處貼上腳本結果...',
|
script_results_placeholder: '在此處貼上腳本結果...',
|
||||||
ssms_instructions: {
|
ssms_instructions: {
|
||||||
button_text: 'SSMS 操作步驟',
|
button_text: 'SSMS 操作步驟',
|
||||||
@@ -327,6 +346,12 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
scale_4x: '4x',
|
scale_4x: '4x',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
export: '匯出',
|
export: '匯出',
|
||||||
|
// TODO: Translate
|
||||||
|
advanced_options: 'Advanced Options',
|
||||||
|
pattern: 'Include background pattern',
|
||||||
|
pattern_description: 'Add subtle grid pattern to background.',
|
||||||
|
transparent: 'Transparent background',
|
||||||
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
@@ -359,7 +384,7 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: 'Error exporting diagram',
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
'Something went wrong. Need help? support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -371,7 +396,7 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
error: {
|
error: {
|
||||||
title: '匯入圖表時發生錯誤',
|
title: '匯入圖表時發生錯誤',
|
||||||
description:
|
description:
|
||||||
'圖表的 JSON 無效。請檢查 JSON 並再試一次。如需幫助,請聯繫 chartdb.io@gmail.com',
|
'圖表的 JSON 無效。請檢查 JSON 並再試一次。如需幫助,請聯繫 support@chartdb.io',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -398,6 +423,8 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '新建表格',
|
new_table: '新建表格',
|
||||||
new_relationship: '新建關聯',
|
new_relationship: '新建關聯',
|
||||||
|
// TODO: Translate
|
||||||
|
new_area: 'New Area',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { Area } from './domain/area';
|
||||||
import type { DBDependency } from './domain/db-dependency';
|
import type { DBDependency } from './domain/db-dependency';
|
||||||
import type { DBField } from './domain/db-field';
|
import type { DBField } from './domain/db-field';
|
||||||
import type { DBIndex } from './domain/db-index';
|
import type { DBIndex } from './domain/db-index';
|
||||||
@@ -43,6 +44,10 @@ const generateIdsMapFromDiagram = (
|
|||||||
idsMap.set(dependency.id, generateId());
|
idsMap.set(dependency.id, generateId());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
diagram.areas?.forEach((area) => {
|
||||||
|
idsMap.set(area.id, generateId());
|
||||||
|
});
|
||||||
|
|
||||||
return idsMap;
|
return idsMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,12 +198,28 @@ export const cloneDiagram = (
|
|||||||
(dependency): dependency is DBDependency => dependency !== null
|
(dependency): dependency is DBDependency => dependency !== null
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
|
const areas: Area[] =
|
||||||
|
diagram.areas
|
||||||
|
?.map((area) => {
|
||||||
|
const id = getNewId(area.id);
|
||||||
|
if (!id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...area,
|
||||||
|
id,
|
||||||
|
} satisfies Area;
|
||||||
|
})
|
||||||
|
.filter((area): area is Area => area !== null) ?? [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...diagram,
|
...diagram,
|
||||||
id: diagramId,
|
id: diagramId,
|
||||||
dependencies,
|
dependencies,
|
||||||
relationships,
|
relationships,
|
||||||
tables,
|
tables,
|
||||||
|
areas,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface DataType {
|
|||||||
|
|
||||||
export interface DataTypeData extends DataType {
|
export interface DataTypeData extends DataType {
|
||||||
hasCharMaxLength?: boolean;
|
hasCharMaxLength?: boolean;
|
||||||
|
usageLevel?: 1 | 2; // Level 1 is most common, Level 2 is second most common
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dataTypeSchema: z.ZodType<DataType> = z.object({
|
export const dataTypeSchema: z.ZodType<DataType> = z.object({
|
||||||
@@ -33,6 +34,45 @@ export const dataTypeMap: Record<DatabaseType, readonly DataTypeData[]> = {
|
|||||||
[DatabaseType.COCKROACHDB]: postgresDataTypes,
|
[DatabaseType.COCKROACHDB]: postgresDataTypes,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const sortDataTypes = (dataTypes: DataTypeData[]): DataTypeData[] => {
|
||||||
|
const types = [...dataTypes];
|
||||||
|
return types.sort((a, b) => {
|
||||||
|
// First sort by usage level (lower numbers first)
|
||||||
|
if ((a.usageLevel || 3) < (b.usageLevel || 3)) return -1;
|
||||||
|
if ((a.usageLevel || 3) > (b.usageLevel || 3)) return 1;
|
||||||
|
// Then sort alphabetically by name
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortedDataTypeMap: Record<DatabaseType, readonly DataTypeData[]> =
|
||||||
|
{
|
||||||
|
[DatabaseType.GENERIC]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.GENERIC],
|
||||||
|
]),
|
||||||
|
[DatabaseType.POSTGRESQL]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.POSTGRESQL],
|
||||||
|
]),
|
||||||
|
[DatabaseType.MYSQL]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.MYSQL],
|
||||||
|
]),
|
||||||
|
[DatabaseType.SQL_SERVER]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.SQL_SERVER],
|
||||||
|
]),
|
||||||
|
[DatabaseType.MARIADB]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.MARIADB],
|
||||||
|
]),
|
||||||
|
[DatabaseType.SQLITE]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.SQLITE],
|
||||||
|
]),
|
||||||
|
[DatabaseType.CLICKHOUSE]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.CLICKHOUSE],
|
||||||
|
]),
|
||||||
|
[DatabaseType.COCKROACHDB]: sortDataTypes([
|
||||||
|
...dataTypeMap[DatabaseType.COCKROACHDB],
|
||||||
|
]),
|
||||||
|
} as const;
|
||||||
|
|
||||||
const compatibleTypes: Record<DatabaseType, Record<string, string[]>> = {
|
const compatibleTypes: Record<DatabaseType, Record<string, string[]>> = {
|
||||||
[DatabaseType.POSTGRESQL]: {
|
[DatabaseType.POSTGRESQL]: {
|
||||||
serial: ['integer'],
|
serial: ['integer'],
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
import type { DataTypeData } from './data-types';
|
import type { DataTypeData } from './data-types';
|
||||||
|
|
||||||
export const genericDataTypes: readonly DataTypeData[] = [
|
export const genericDataTypes: readonly DataTypeData[] = [
|
||||||
|
// Level 1 - Most commonly used types
|
||||||
|
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||||
|
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||||
|
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||||
|
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||||
|
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||||
|
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||||
|
|
||||||
|
// Level 2 - Second most common types
|
||||||
|
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||||
|
{ name: 'datetime', id: 'datetime', usageLevel: 2 },
|
||||||
|
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||||
|
{ name: 'uuid', id: 'uuid', usageLevel: 2 },
|
||||||
|
|
||||||
|
// Less common types
|
||||||
{ name: 'bigint', id: 'bigint' },
|
{ name: 'bigint', id: 'bigint' },
|
||||||
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
||||||
{ name: 'blob', id: 'blob' },
|
{ name: 'blob', id: 'blob' },
|
||||||
{ name: 'boolean', id: 'boolean' },
|
|
||||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||||
{ name: 'date', id: 'date' },
|
|
||||||
{ name: 'datetime', id: 'datetime' },
|
|
||||||
{ name: 'decimal', id: 'decimal' },
|
|
||||||
{ name: 'double', id: 'double' },
|
{ name: 'double', id: 'double' },
|
||||||
{ name: 'enum', id: 'enum' },
|
{ name: 'enum', id: 'enum' },
|
||||||
{ name: 'float', id: 'float' },
|
{ name: 'float', id: 'float' },
|
||||||
{ name: 'int', id: 'int' },
|
|
||||||
{ name: 'json', id: 'json' },
|
|
||||||
{ name: 'numeric', id: 'numeric' },
|
{ name: 'numeric', id: 'numeric' },
|
||||||
{ name: 'real', id: 'real' },
|
{ name: 'real', id: 'real' },
|
||||||
{ name: 'set', id: 'set' },
|
{ name: 'set', id: 'set' },
|
||||||
{ name: 'smallint', id: 'smallint' },
|
{ name: 'smallint', id: 'smallint' },
|
||||||
{ name: 'text', id: 'text' },
|
|
||||||
{ name: 'time', id: 'time' },
|
{ name: 'time', id: 'time' },
|
||||||
{ name: 'timestamp', id: 'timestamp' },
|
|
||||||
{ name: 'uuid', id: 'uuid' },
|
|
||||||
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
||||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
import type { DataTypeData } from './data-types';
|
import type { DataTypeData } from './data-types';
|
||||||
|
|
||||||
export const mariadbDataTypes: readonly DataTypeData[] = [
|
export const mariadbDataTypes: readonly DataTypeData[] = [
|
||||||
// Numeric Types
|
// Level 1 - Most commonly used types
|
||||||
|
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||||
|
{ name: 'bigint', id: 'bigint', usageLevel: 1 },
|
||||||
|
{ name: 'decimal', id: 'decimal', usageLevel: 1 },
|
||||||
|
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||||
|
{ name: 'datetime', id: 'datetime', usageLevel: 1 },
|
||||||
|
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||||
|
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||||
|
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||||
|
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||||
|
|
||||||
|
// Level 2 - Second most common types
|
||||||
|
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||||
|
{ name: 'uuid', id: 'uuid', usageLevel: 2 },
|
||||||
|
|
||||||
|
// Less common types
|
||||||
{ name: 'tinyint', id: 'tinyint' },
|
{ name: 'tinyint', id: 'tinyint' },
|
||||||
{ name: 'smallint', id: 'smallint' },
|
{ name: 'smallint', id: 'smallint' },
|
||||||
{ name: 'mediumint', id: 'mediumint' },
|
{ name: 'mediumint', id: 'mediumint' },
|
||||||
{ name: 'int', id: 'int' },
|
|
||||||
{ name: 'bigint', id: 'bigint' },
|
|
||||||
{ name: 'decimal', id: 'decimal' },
|
|
||||||
{ name: 'numeric', id: 'numeric' },
|
{ name: 'numeric', id: 'numeric' },
|
||||||
{ name: 'float', id: 'float' },
|
{ name: 'float', id: 'float' },
|
||||||
{ name: 'double', id: 'double' },
|
{ name: 'double', id: 'double' },
|
||||||
{ name: 'bit', id: 'bit' },
|
{ name: 'bit', id: 'bit' },
|
||||||
{ name: 'bool', id: 'bool' },
|
{ name: 'bool', id: 'bool' },
|
||||||
{ name: 'boolean', id: 'boolean' },
|
|
||||||
|
|
||||||
// Date and Time Types
|
|
||||||
{ name: 'date', id: 'date' },
|
|
||||||
{ name: 'datetime', id: 'datetime' },
|
|
||||||
{ name: 'timestamp', id: 'timestamp' },
|
|
||||||
{ name: 'time', id: 'time' },
|
{ name: 'time', id: 'time' },
|
||||||
{ name: 'year', id: 'year' },
|
{ name: 'year', id: 'year' },
|
||||||
|
|
||||||
// String Types
|
|
||||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true },
|
|
||||||
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
||||||
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
||||||
{ name: 'tinyblob', id: 'tinyblob' },
|
{ name: 'tinyblob', id: 'tinyblob' },
|
||||||
@@ -32,13 +35,10 @@ export const mariadbDataTypes: readonly DataTypeData[] = [
|
|||||||
{ name: 'mediumblob', id: 'mediumblob' },
|
{ name: 'mediumblob', id: 'mediumblob' },
|
||||||
{ name: 'longblob', id: 'longblob' },
|
{ name: 'longblob', id: 'longblob' },
|
||||||
{ name: 'tinytext', id: 'tinytext' },
|
{ name: 'tinytext', id: 'tinytext' },
|
||||||
{ name: 'text', id: 'text' },
|
|
||||||
{ name: 'mediumtext', id: 'mediumtext' },
|
{ name: 'mediumtext', id: 'mediumtext' },
|
||||||
{ name: 'longtext', id: 'longtext' },
|
{ name: 'longtext', id: 'longtext' },
|
||||||
{ name: 'enum', id: 'enum' },
|
{ name: 'enum', id: 'enum' },
|
||||||
{ name: 'set', id: 'set' },
|
{ name: 'set', id: 'set' },
|
||||||
|
|
||||||
// Spatial Types
|
|
||||||
{ name: 'geometry', id: 'geometry' },
|
{ name: 'geometry', id: 'geometry' },
|
||||||
{ name: 'point', id: 'point' },
|
{ name: 'point', id: 'point' },
|
||||||
{ name: 'linestring', id: 'linestring' },
|
{ name: 'linestring', id: 'linestring' },
|
||||||
@@ -47,8 +47,4 @@ export const mariadbDataTypes: readonly DataTypeData[] = [
|
|||||||
{ name: 'multilinestring', id: 'multilinestring' },
|
{ name: 'multilinestring', id: 'multilinestring' },
|
||||||
{ name: 'multipolygon', id: 'multipolygon' },
|
{ name: 'multipolygon', id: 'multipolygon' },
|
||||||
{ name: 'geometrycollection', id: 'geometrycollection' },
|
{ name: 'geometrycollection', id: 'geometrycollection' },
|
||||||
|
|
||||||
// JSON Type
|
|
||||||
{ name: 'json', id: 'json' },
|
|
||||||
{ name: 'uuid', id: 'uuid' },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,44 +1,41 @@
|
|||||||
import type { DataTypeData } from './data-types';
|
import type { DataTypeData } from './data-types';
|
||||||
|
|
||||||
export const mysqlDataTypes: readonly DataTypeData[] = [
|
export const mysqlDataTypes: readonly DataTypeData[] = [
|
||||||
// Numeric Types
|
// Level 1 - Most commonly used types
|
||||||
|
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||||
|
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||||
|
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||||
|
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||||
|
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||||
|
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||||
|
|
||||||
|
// Level 2 - Second most common types
|
||||||
|
{ name: 'bigint', id: 'bigint', usageLevel: 2 },
|
||||||
|
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||||
|
{ name: 'datetime', id: 'datetime', usageLevel: 2 },
|
||||||
|
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||||
|
|
||||||
|
// Less common types
|
||||||
{ name: 'tinyint', id: 'tinyint' },
|
{ name: 'tinyint', id: 'tinyint' },
|
||||||
{ name: 'smallint', id: 'smallint' },
|
{ name: 'smallint', id: 'smallint' },
|
||||||
{ name: 'mediumint', id: 'mediumint' },
|
{ name: 'mediumint', id: 'mediumint' },
|
||||||
{ name: 'int', id: 'int' },
|
|
||||||
{ name: 'bigint', id: 'bigint' },
|
|
||||||
{ name: 'decimal', id: 'decimal' },
|
|
||||||
{ name: 'numeric', id: 'numeric' },
|
|
||||||
{ name: 'float', id: 'float' },
|
{ name: 'float', id: 'float' },
|
||||||
{ name: 'double', id: 'double' },
|
{ name: 'double', id: 'double' },
|
||||||
{ name: 'bit', id: 'bit' },
|
{ name: 'bit', id: 'bit' },
|
||||||
{ name: 'bool', id: 'bool' },
|
|
||||||
{ name: 'boolean', id: 'boolean' },
|
|
||||||
|
|
||||||
// Date and Time Types
|
|
||||||
{ name: 'date', id: 'date' },
|
|
||||||
{ name: 'datetime', id: 'datetime' },
|
|
||||||
{ name: 'timestamp', id: 'timestamp' },
|
|
||||||
{ name: 'time', id: 'time' },
|
|
||||||
{ name: 'year', id: 'year' },
|
|
||||||
|
|
||||||
// String Types
|
|
||||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true },
|
{ name: 'tinytext', id: 'tinytext' },
|
||||||
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
{ name: 'mediumtext', id: 'mediumtext' },
|
||||||
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
{ name: 'longtext', id: 'longtext' },
|
||||||
|
{ name: 'binary', id: 'binary' },
|
||||||
|
{ name: 'varbinary', id: 'varbinary' },
|
||||||
{ name: 'tinyblob', id: 'tinyblob' },
|
{ name: 'tinyblob', id: 'tinyblob' },
|
||||||
{ name: 'blob', id: 'blob' },
|
{ name: 'blob', id: 'blob' },
|
||||||
{ name: 'mediumblob', id: 'mediumblob' },
|
{ name: 'mediumblob', id: 'mediumblob' },
|
||||||
{ name: 'longblob', id: 'longblob' },
|
{ name: 'longblob', id: 'longblob' },
|
||||||
{ name: 'tinytext', id: 'tinytext' },
|
|
||||||
{ name: 'text', id: 'text' },
|
|
||||||
{ name: 'mediumtext', id: 'mediumtext' },
|
|
||||||
{ name: 'longtext', id: 'longtext' },
|
|
||||||
{ name: 'enum', id: 'enum' },
|
{ name: 'enum', id: 'enum' },
|
||||||
{ name: 'set', id: 'set' },
|
{ name: 'set', id: 'set' },
|
||||||
|
{ name: 'time', id: 'time' },
|
||||||
// Spatial Types
|
{ name: 'year', id: 'year' },
|
||||||
{ name: 'geometry', id: 'geometry' },
|
{ name: 'geometry', id: 'geometry' },
|
||||||
{ name: 'point', id: 'point' },
|
{ name: 'point', id: 'point' },
|
||||||
{ name: 'linestring', id: 'linestring' },
|
{ name: 'linestring', id: 'linestring' },
|
||||||
@@ -47,7 +44,4 @@ export const mysqlDataTypes: readonly DataTypeData[] = [
|
|||||||
{ name: 'multilinestring', id: 'multilinestring' },
|
{ name: 'multilinestring', id: 'multilinestring' },
|
||||||
{ name: 'multipolygon', id: 'multipolygon' },
|
{ name: 'multipolygon', id: 'multipolygon' },
|
||||||
{ name: 'geometrycollection', id: 'geometrycollection' },
|
{ name: 'geometrycollection', id: 'geometrycollection' },
|
||||||
|
|
||||||
// JSON Type
|
|
||||||
{ name: 'json', id: 'json' },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,49 +1,48 @@
|
|||||||
import type { DataTypeData } from './data-types';
|
import type { DataTypeData } from './data-types';
|
||||||
|
|
||||||
export const postgresDataTypes: readonly DataTypeData[] = [
|
export const postgresDataTypes: readonly DataTypeData[] = [
|
||||||
// Numeric Types
|
// Level 1 - Most commonly used types
|
||||||
{ name: 'smallint', id: 'smallint' },
|
{ name: 'integer', id: 'integer', usageLevel: 1 },
|
||||||
{ name: 'integer', id: 'integer' },
|
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||||
{ name: 'bigint', id: 'bigint' },
|
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||||
{ name: 'decimal', id: 'decimal' },
|
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||||
|
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||||
|
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||||
|
|
||||||
|
// Level 2 - Second most common types
|
||||||
|
{ name: 'bigint', id: 'bigint', usageLevel: 2 },
|
||||||
|
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||||
|
{ name: 'serial', id: 'serial', usageLevel: 2 },
|
||||||
|
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||||
|
{ name: 'jsonb', id: 'jsonb', usageLevel: 2 },
|
||||||
|
{ name: 'uuid', id: 'uuid', usageLevel: 2 },
|
||||||
|
{
|
||||||
|
name: 'timestamp with time zone',
|
||||||
|
id: 'timestamp_with_time_zone',
|
||||||
|
usageLevel: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Less common types
|
||||||
{ name: 'numeric', id: 'numeric' },
|
{ name: 'numeric', id: 'numeric' },
|
||||||
{ name: 'real', id: 'real' },
|
{ name: 'real', id: 'real' },
|
||||||
{ name: 'double precision', id: 'double_precision' },
|
{ name: 'double precision', id: 'double_precision' },
|
||||||
{ name: 'smallserial', id: 'smallserial' },
|
{ name: 'smallserial', id: 'smallserial' },
|
||||||
{ name: 'serial', id: 'serial' },
|
|
||||||
{ name: 'bigserial', id: 'bigserial' },
|
{ name: 'bigserial', id: 'bigserial' },
|
||||||
{ name: 'money', id: 'money' },
|
{ name: 'money', id: 'money' },
|
||||||
|
{ name: 'smallint', id: 'smallint' },
|
||||||
// Character Types
|
|
||||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true },
|
|
||||||
{
|
{
|
||||||
name: 'character varying',
|
name: 'character varying',
|
||||||
id: 'character_varying',
|
id: 'character_varying',
|
||||||
hasCharMaxLength: true,
|
hasCharMaxLength: true,
|
||||||
},
|
},
|
||||||
{ name: 'text', id: 'text' },
|
|
||||||
|
|
||||||
// Binary Data Types
|
|
||||||
{ name: 'bytea', id: 'bytea' },
|
|
||||||
|
|
||||||
// Date/Time Types
|
|
||||||
{ name: 'date', id: 'date' },
|
|
||||||
{ name: 'timestamp', id: 'timestamp' },
|
|
||||||
{ name: 'timestamp with time zone', id: 'timestamp_with_time_zone' },
|
|
||||||
{ name: 'timestamp without time zone', id: 'timestamp_without_time_zone' },
|
|
||||||
{ name: 'time', id: 'time' },
|
{ name: 'time', id: 'time' },
|
||||||
|
{ name: 'timestamp without time zone', id: 'timestamp_without_time_zone' },
|
||||||
{ name: 'time with time zone', id: 'time_with_time_zone' },
|
{ name: 'time with time zone', id: 'time_with_time_zone' },
|
||||||
{ name: 'time without time zone', id: 'time_without_time_zone' },
|
{ name: 'time without time zone', id: 'time_without_time_zone' },
|
||||||
{ name: 'interval', id: 'interval' },
|
{ name: 'interval', id: 'interval' },
|
||||||
|
{ name: 'bytea', id: 'bytea' },
|
||||||
// Boolean Type
|
|
||||||
{ name: 'boolean', id: 'boolean' },
|
|
||||||
|
|
||||||
// Enumerated Types
|
|
||||||
{ name: 'enum', id: 'enum' },
|
{ name: 'enum', id: 'enum' },
|
||||||
|
|
||||||
// Geometric Types
|
|
||||||
{ name: 'point', id: 'point' },
|
{ name: 'point', id: 'point' },
|
||||||
{ name: 'line', id: 'line' },
|
{ name: 'line', id: 'line' },
|
||||||
{ name: 'lseg', id: 'lseg' },
|
{ name: 'lseg', id: 'lseg' },
|
||||||
@@ -51,43 +50,22 @@ export const postgresDataTypes: readonly DataTypeData[] = [
|
|||||||
{ name: 'path', id: 'path' },
|
{ name: 'path', id: 'path' },
|
||||||
{ name: 'polygon', id: 'polygon' },
|
{ name: 'polygon', id: 'polygon' },
|
||||||
{ name: 'circle', id: 'circle' },
|
{ name: 'circle', id: 'circle' },
|
||||||
|
|
||||||
// Network Address Types
|
|
||||||
{ name: 'cidr', id: 'cidr' },
|
{ name: 'cidr', id: 'cidr' },
|
||||||
{ name: 'inet', id: 'inet' },
|
{ name: 'inet', id: 'inet' },
|
||||||
{ name: 'macaddr', id: 'macaddr' },
|
{ name: 'macaddr', id: 'macaddr' },
|
||||||
{ name: 'macaddr8', id: 'macaddr8' },
|
{ name: 'macaddr8', id: 'macaddr8' },
|
||||||
|
|
||||||
// Bit String Types
|
|
||||||
{ name: 'bit', id: 'bit' },
|
{ name: 'bit', id: 'bit' },
|
||||||
{ name: 'bit varying', id: 'bit_varying' },
|
{ name: 'bit varying', id: 'bit_varying' },
|
||||||
|
|
||||||
// Text Search Types
|
|
||||||
{ name: 'tsvector', id: 'tsvector' },
|
{ name: 'tsvector', id: 'tsvector' },
|
||||||
{ name: 'tsquery', id: 'tsquery' },
|
{ name: 'tsquery', id: 'tsquery' },
|
||||||
|
|
||||||
// UUID Type
|
|
||||||
{ name: 'uuid', id: 'uuid' },
|
|
||||||
|
|
||||||
// XML Type
|
|
||||||
{ name: 'xml', id: 'xml' },
|
{ name: 'xml', id: 'xml' },
|
||||||
|
|
||||||
// JSON Types
|
|
||||||
{ name: 'json', id: 'json' },
|
|
||||||
{ name: 'jsonb', id: 'jsonb' },
|
|
||||||
|
|
||||||
// Array Types
|
|
||||||
{ name: 'array', id: 'array' },
|
{ name: 'array', id: 'array' },
|
||||||
|
|
||||||
// Range Types
|
|
||||||
{ name: 'int4range', id: 'int4range' },
|
{ name: 'int4range', id: 'int4range' },
|
||||||
{ name: 'int8range', id: 'int8range' },
|
{ name: 'int8range', id: 'int8range' },
|
||||||
{ name: 'numrange', id: 'numrange' },
|
{ name: 'numrange', id: 'numrange' },
|
||||||
{ name: 'tsrange', id: 'tsrange' },
|
{ name: 'tsrange', id: 'tsrange' },
|
||||||
{ name: 'tstzrange', id: 'tstzrange' },
|
{ name: 'tstzrange', id: 'tstzrange' },
|
||||||
{ name: 'daterange', id: 'daterange' },
|
{ name: 'daterange', id: 'daterange' },
|
||||||
|
|
||||||
// Object Identifier Types
|
|
||||||
{ name: 'oid', id: 'oid' },
|
{ name: 'oid', id: 'oid' },
|
||||||
{ name: 'regproc', id: 'regproc' },
|
{ name: 'regproc', id: 'regproc' },
|
||||||
{ name: 'regprocedure', id: 'regprocedure' },
|
{ name: 'regprocedure', id: 'regprocedure' },
|
||||||
@@ -99,7 +77,5 @@ export const postgresDataTypes: readonly DataTypeData[] = [
|
|||||||
{ name: 'regnamespace', id: 'regnamespace' },
|
{ name: 'regnamespace', id: 'regnamespace' },
|
||||||
{ name: 'regconfig', id: 'regconfig' },
|
{ name: 'regconfig', id: 'regconfig' },
|
||||||
{ name: 'regdictionary', id: 'regdictionary' },
|
{ name: 'regdictionary', id: 'regdictionary' },
|
||||||
|
|
||||||
// User Defined types
|
|
||||||
{ name: 'user-defined', id: 'user-defined' },
|
{ name: 'user-defined', id: 'user-defined' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,56 +1,44 @@
|
|||||||
import type { DataTypeData } from './data-types';
|
import type { DataTypeData } from './data-types';
|
||||||
|
|
||||||
export const sqlServerDataTypes: readonly DataTypeData[] = [
|
export const sqlServerDataTypes: readonly DataTypeData[] = [
|
||||||
// Exact Numerics
|
// Level 1 - Most commonly used types
|
||||||
{ name: 'bigint', id: 'bigint' },
|
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||||
{ name: 'bit', id: 'bit' },
|
{ name: 'bit', id: 'bit', usageLevel: 1 },
|
||||||
{ name: 'decimal', id: 'decimal' },
|
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||||
{ name: 'int', id: 'int' },
|
{ name: 'nvarchar', id: 'nvarchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||||
{ name: 'money', id: 'money' },
|
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||||
|
{ name: 'datetime', id: 'datetime', usageLevel: 1 },
|
||||||
|
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||||
|
|
||||||
|
// Level 2 - Second most common types
|
||||||
|
{ name: 'bigint', id: 'bigint', usageLevel: 2 },
|
||||||
|
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||||
|
{ name: 'datetime2', id: 'datetime2', usageLevel: 2 },
|
||||||
|
{ name: 'uniqueidentifier', id: 'uniqueidentifier', usageLevel: 2 },
|
||||||
|
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||||
|
|
||||||
|
// Less common types
|
||||||
{ name: 'numeric', id: 'numeric' },
|
{ name: 'numeric', id: 'numeric' },
|
||||||
{ name: 'smallint', id: 'smallint' },
|
{ name: 'smallint', id: 'smallint' },
|
||||||
{ name: 'smallmoney', id: 'smallmoney' },
|
{ name: 'smallmoney', id: 'smallmoney' },
|
||||||
{ name: 'tinyint', id: 'tinyint' },
|
{ name: 'tinyint', id: 'tinyint' },
|
||||||
|
{ name: 'money', id: 'money' },
|
||||||
// Approximate Numerics
|
|
||||||
{ name: 'float', id: 'float' },
|
{ name: 'float', id: 'float' },
|
||||||
{ name: 'real', id: 'real' },
|
{ name: 'real', id: 'real' },
|
||||||
|
|
||||||
// Date and Time
|
|
||||||
{ name: 'date', id: 'date' },
|
|
||||||
{ name: 'datetime2', id: 'datetime2' },
|
|
||||||
{ name: 'datetime', id: 'datetime' },
|
|
||||||
{ name: 'datetimeoffset', id: 'datetimeoffset' },
|
|
||||||
{ name: 'smalldatetime', id: 'smalldatetime' },
|
|
||||||
{ name: 'time', id: 'time' },
|
|
||||||
|
|
||||||
// Character Strings
|
|
||||||
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true },
|
|
||||||
{ name: 'text', id: 'text' },
|
|
||||||
|
|
||||||
// Unicode Character Strings
|
|
||||||
{ name: 'nchar', id: 'nchar', hasCharMaxLength: true },
|
{ name: 'nchar', id: 'nchar', hasCharMaxLength: true },
|
||||||
{ name: 'nvarchar', id: 'nvarchar', hasCharMaxLength: true },
|
|
||||||
{ name: 'ntext', id: 'ntext' },
|
{ name: 'ntext', id: 'ntext' },
|
||||||
|
|
||||||
// Binary Strings
|
|
||||||
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
{ name: 'binary', id: 'binary', hasCharMaxLength: true },
|
||||||
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true },
|
||||||
{ name: 'image', id: 'image' },
|
{ name: 'image', id: 'image' },
|
||||||
|
{ name: 'datetimeoffset', id: 'datetimeoffset' },
|
||||||
// Other Data Types
|
{ name: 'smalldatetime', id: 'smalldatetime' },
|
||||||
|
{ name: 'time', id: 'time' },
|
||||||
|
{ name: 'timestamp', id: 'timestamp' },
|
||||||
|
{ name: 'xml', id: 'xml' },
|
||||||
{ name: 'cursor', id: 'cursor' },
|
{ name: 'cursor', id: 'cursor' },
|
||||||
{ name: 'hierarchyid', id: 'hierarchyid' },
|
{ name: 'hierarchyid', id: 'hierarchyid' },
|
||||||
{ name: 'sql_variant', id: 'sql_variant' },
|
{ name: 'sql_variant', id: 'sql_variant' },
|
||||||
{ name: 'timestamp', id: 'timestamp' },
|
|
||||||
{ name: 'uniqueidentifier', id: 'uniqueidentifier' },
|
|
||||||
{ name: 'xml', id: 'xml' },
|
|
||||||
|
|
||||||
// Spatial Data Types
|
|
||||||
{ name: 'geometry', id: 'geometry' },
|
{ name: 'geometry', id: 'geometry' },
|
||||||
{ name: 'geography', id: 'geography' },
|
{ name: 'geography', id: 'geography' },
|
||||||
|
|
||||||
// JSON
|
|
||||||
{ name: 'json', id: 'json' },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,27 +1,34 @@
|
|||||||
import type { DataTypeData } from './data-types';
|
import type { DataTypeData } from './data-types';
|
||||||
|
|
||||||
export const sqliteDataTypes: readonly DataTypeData[] = [
|
export const sqliteDataTypes: readonly DataTypeData[] = [
|
||||||
// Numeric Types
|
// Level 1 - Most commonly used types - SQLite's 5 storage classes
|
||||||
{ name: 'integer', id: 'integer' },
|
{ name: 'integer', id: 'integer', usageLevel: 1 },
|
||||||
{ name: 'real', id: 'real' },
|
{ name: 'text', id: 'text', usageLevel: 1 },
|
||||||
{ name: 'numeric', id: 'numeric' },
|
{ name: 'real', id: 'real', usageLevel: 1 },
|
||||||
|
{ name: 'blob', id: 'blob', usageLevel: 1 },
|
||||||
|
{ name: 'null', id: 'null', usageLevel: 1 },
|
||||||
|
|
||||||
// Text Type
|
// SQLite type aliases and common types
|
||||||
{ name: 'text', id: 'text' },
|
{ name: 'int', id: 'int', usageLevel: 1 },
|
||||||
|
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 },
|
||||||
|
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 },
|
||||||
|
{ name: 'date', id: 'date', usageLevel: 1 },
|
||||||
|
{ name: 'datetime', id: 'datetime', usageLevel: 1 },
|
||||||
|
{ name: 'boolean', id: 'boolean', usageLevel: 1 },
|
||||||
|
|
||||||
// Blob Type
|
// Level 2 - Second most common types
|
||||||
{ name: 'blob', id: 'blob' },
|
{ name: 'numeric', id: 'numeric', usageLevel: 2 },
|
||||||
|
{ name: 'decimal', id: 'decimal', usageLevel: 2 },
|
||||||
|
{ name: 'float', id: 'float', usageLevel: 2 },
|
||||||
|
{ name: 'double', id: 'double', usageLevel: 2 },
|
||||||
|
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||||
|
|
||||||
// Blob Type
|
// Less common types (all map to SQLite storage classes)
|
||||||
{ name: 'json', id: 'json' },
|
{ name: 'char', id: 'char', hasCharMaxLength: true },
|
||||||
|
{ name: 'binary', id: 'binary' },
|
||||||
// Date/Time Types (SQLite uses TEXT, REAL, or INTEGER types for dates and times)
|
{ name: 'varbinary', id: 'varbinary' },
|
||||||
{ name: 'date', id: 'date' },
|
{ name: 'smallint', id: 'smallint' },
|
||||||
{ name: 'datetime', id: 'datetime' },
|
{ name: 'bigint', id: 'bigint' },
|
||||||
|
{ name: 'bool', id: 'bool' },
|
||||||
{ name: 'int', id: 'int' },
|
{ name: 'time', id: 'time' },
|
||||||
{ name: 'float', id: 'float' },
|
|
||||||
{ name: 'boolean', id: 'boolean' },
|
|
||||||
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true },
|
|
||||||
{ name: 'decimal', id: 'decimal' },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { DBField } from '@/lib/domain/db-field';
|
|||||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
|
|
||||||
function parsePostgresDefault(field: DBField): string {
|
function parsePostgresDefault(field: DBField): string {
|
||||||
if (!field.default) {
|
if (!field.default || typeof field.default !== 'string') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +165,21 @@ export function exportPostgreSQL(diagram: Diagram): string {
|
|||||||
|
|
||||||
// Handle PostgreSQL specific type formatting
|
// Handle PostgreSQL specific type formatting
|
||||||
let typeWithSize = typeName;
|
let typeWithSize = typeName;
|
||||||
|
let serialType = null;
|
||||||
|
|
||||||
|
if (field.increment && !field.nullable) {
|
||||||
|
if (
|
||||||
|
typeName.toLowerCase() === 'integer' ||
|
||||||
|
typeName.toLowerCase() === 'int'
|
||||||
|
) {
|
||||||
|
serialType = 'SERIAL';
|
||||||
|
} else if (typeName.toLowerCase() === 'bigint') {
|
||||||
|
serialType = 'BIGSERIAL';
|
||||||
|
} else if (typeName.toLowerCase() === 'smallint') {
|
||||||
|
serialType = 'SMALLSERIAL';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (field.characterMaximumLength) {
|
if (field.characterMaximumLength) {
|
||||||
if (
|
if (
|
||||||
typeName.toLowerCase() === 'varchar' ||
|
typeName.toLowerCase() === 'varchar' ||
|
||||||
@@ -221,7 +236,7 @@ export function exportPostgreSQL(diagram: Diagram): string {
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
// Do not add PRIMARY KEY as a column constraint - will add as table constraint
|
// Do not add PRIMARY KEY as a column constraint - will add as table constraint
|
||||||
return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${typeWithSize}${notNull}${identity}${unique}${defaultValue}`;
|
return `${exportFieldComment(field.comments ?? '')} ${fieldName} ${serialType || typeWithSize}${serialType ? '' : notNull}${identity}${unique}${defaultValue}`;
|
||||||
})
|
})
|
||||||
.join(',\n')}${
|
.join(',\n')}${
|
||||||
primaryKeyFields.length > 0
|
primaryKeyFields.length > 0
|
||||||
|
|||||||
@@ -9,6 +9,29 @@ import { exportPostgreSQL } from './export-per-type/postgresql';
|
|||||||
import { exportSQLite } from './export-per-type/sqlite';
|
import { exportSQLite } from './export-per-type/sqlite';
|
||||||
import { exportMySQL } from './export-per-type/mysql';
|
import { exportMySQL } from './export-per-type/mysql';
|
||||||
|
|
||||||
|
// Function to simplify verbose data type names
|
||||||
|
const simplifyDataType = (typeName: string): string => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
'character varying': 'varchar',
|
||||||
|
'char varying': 'varchar',
|
||||||
|
integer: 'int',
|
||||||
|
int4: 'int',
|
||||||
|
int8: 'bigint',
|
||||||
|
serial4: 'serial',
|
||||||
|
serial8: 'bigserial',
|
||||||
|
float8: 'double precision',
|
||||||
|
float4: 'real',
|
||||||
|
bool: 'boolean',
|
||||||
|
character: 'char',
|
||||||
|
'timestamp without time zone': 'timestamp',
|
||||||
|
'timestamp with time zone': 'timestamptz',
|
||||||
|
'time without time zone': 'time',
|
||||||
|
'time with time zone': 'timetz',
|
||||||
|
};
|
||||||
|
|
||||||
|
return typeMap[typeName.toLowerCase()] || typeName;
|
||||||
|
};
|
||||||
|
|
||||||
export const exportBaseSQL = ({
|
export const exportBaseSQL = ({
|
||||||
diagram,
|
diagram,
|
||||||
targetDatabaseType,
|
targetDatabaseType,
|
||||||
@@ -93,11 +116,12 @@ export const exportBaseSQL = ({
|
|||||||
sqlScript += `CREATE TABLE ${tableName} (\n`;
|
sqlScript += `CREATE TABLE ${tableName} (\n`;
|
||||||
|
|
||||||
table.fields.forEach((field, index) => {
|
table.fields.forEach((field, index) => {
|
||||||
let typeName = field.type.name;
|
let typeName = simplifyDataType(field.type.name);
|
||||||
|
|
||||||
// Handle ENUM type
|
// Handle ENUM type
|
||||||
if (typeName.toLowerCase() === 'enum') {
|
if (typeName.toLowerCase() === 'enum') {
|
||||||
typeName = 'varchar';
|
// Map enum to TEXT for broader compatibility, especially with DBML importer
|
||||||
|
typeName = 'text';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temp fix for 'array' to be text[]
|
// Temp fix for 'array' to be text[]
|
||||||
@@ -116,6 +140,7 @@ export const exportBaseSQL = ({
|
|||||||
if (field.characterMaximumLength) {
|
if (field.characterMaximumLength) {
|
||||||
sqlScript += `(${field.characterMaximumLength})`;
|
sqlScript += `(${field.characterMaximumLength})`;
|
||||||
} else if (field.type.name.toLowerCase().includes('varchar')) {
|
} else if (field.type.name.toLowerCase().includes('varchar')) {
|
||||||
|
// Keep varchar sizing, but don't apply to TEXT (previously enum)
|
||||||
sqlScript += `(500)`;
|
sqlScript += `(500)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface ColumnInfo {
|
export interface ColumnInfo {
|
||||||
schema: string;
|
schema: string;
|
||||||
table: string;
|
table: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
ordinal_position: number;
|
ordinal_position: number;
|
||||||
nullable: boolean;
|
nullable: boolean | number;
|
||||||
character_maximum_length?: string | null; // The maximum length of the column (if applicable), nullable
|
character_maximum_length?: string | null; // The maximum length of the column (if applicable), nullable
|
||||||
precision?: {
|
precision?: {
|
||||||
precision: number | null; // The precision for numeric types
|
precision: number | null; // The precision for numeric types
|
||||||
@@ -14,3 +16,23 @@ export interface ColumnInfo {
|
|||||||
collation?: string | null;
|
collation?: string | null;
|
||||||
comment?: string | null;
|
comment?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ColumnInfoSchema: z.ZodType<ColumnInfo> = z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
table: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
ordinal_position: z.number(),
|
||||||
|
nullable: z.union([z.boolean(), z.number()]),
|
||||||
|
character_maximum_length: z.string().nullable().optional(),
|
||||||
|
precision: z
|
||||||
|
.object({
|
||||||
|
precision: z.number().nullable(),
|
||||||
|
scale: z.number().nullable(),
|
||||||
|
})
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
default: z.string().nullable().optional(),
|
||||||
|
collation: z.string().nullable().optional(),
|
||||||
|
comment: z.string().nullable().optional(),
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import type { ForeignKeyInfo } from './foreign-key-info';
|
import { z } from 'zod';
|
||||||
import type { PrimaryKeyInfo } from './primary-key-info';
|
import { ForeignKeyInfoSchema, type ForeignKeyInfo } from './foreign-key-info';
|
||||||
import type { ColumnInfo } from './column-info';
|
import { PrimaryKeyInfoSchema, type PrimaryKeyInfo } from './primary-key-info';
|
||||||
import type { IndexInfo } from './index-info';
|
import { ColumnInfoSchema, type ColumnInfo } from './column-info';
|
||||||
import type { TableInfo } from './table-info';
|
import { IndexInfoSchema, type IndexInfo } from './index-info';
|
||||||
import type { ViewInfo } from './view-info';
|
import { TableInfoSchema, type TableInfo } from './table-info';
|
||||||
|
import { ViewInfoSchema, type ViewInfo } from './view-info';
|
||||||
|
|
||||||
export interface DatabaseMetadata {
|
export interface DatabaseMetadata {
|
||||||
fk_info: ForeignKeyInfo[];
|
fk_info: ForeignKeyInfo[];
|
||||||
pk_info: PrimaryKeyInfo[];
|
pk_info: PrimaryKeyInfo[];
|
||||||
@@ -15,16 +17,26 @@ export interface DatabaseMetadata {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
export const DatabaseMetadataSchema: z.ZodType<DatabaseMetadata> = z.object({
|
||||||
export const isDatabaseMetadata = (obj: any): boolean => {
|
fk_info: z.array(ForeignKeyInfoSchema),
|
||||||
return (
|
pk_info: z.array(PrimaryKeyInfoSchema),
|
||||||
Array.isArray(obj.fk_info) &&
|
columns: z.array(ColumnInfoSchema),
|
||||||
Array.isArray(obj.pk_info) &&
|
indexes: z.array(IndexInfoSchema),
|
||||||
Array.isArray(obj.columns) &&
|
tables: z.array(TableInfoSchema),
|
||||||
Array.isArray(obj.indexes) &&
|
views: z.array(ViewInfoSchema),
|
||||||
Array.isArray(obj.tables) &&
|
database_name: z.string(),
|
||||||
Array.isArray(obj.views)
|
version: z.string(),
|
||||||
);
|
});
|
||||||
|
|
||||||
|
export const isDatabaseMetadata = (obj: unknown): boolean => {
|
||||||
|
const parsedObject = DatabaseMetadataSchema.safeParse(obj);
|
||||||
|
|
||||||
|
if (!parsedObject.success) {
|
||||||
|
console.error(parsedObject.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadDatabaseMetadata = (jsonString: string): DatabaseMetadata => {
|
export const loadDatabaseMetadata = (jsonString: string): DatabaseMetadata => {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface ForeignKeyInfo {
|
export interface ForeignKeyInfo {
|
||||||
schema: string;
|
schema: string;
|
||||||
table: string;
|
table: string;
|
||||||
@@ -8,3 +10,14 @@ export interface ForeignKeyInfo {
|
|||||||
reference_column: string;
|
reference_column: string;
|
||||||
fk_def: string;
|
fk_def: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ForeignKeyInfoSchema: z.ZodType<ForeignKeyInfo> = z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
table: z.string(),
|
||||||
|
column: z.string(),
|
||||||
|
foreign_key_name: z.string(),
|
||||||
|
reference_schema: z.string().optional(),
|
||||||
|
reference_table: z.string(),
|
||||||
|
reference_column: z.string(),
|
||||||
|
fk_def: z.string(),
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { schemaNameToDomainSchemaName } from '@/lib/domain/db-schema';
|
import { schemaNameToDomainSchemaName } from '@/lib/domain/db-schema';
|
||||||
import type { TableInfo } from './table-info';
|
import type { TableInfo } from './table-info';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface IndexInfo {
|
export interface IndexInfo {
|
||||||
schema: string;
|
schema: string;
|
||||||
@@ -7,14 +8,26 @@ export interface IndexInfo {
|
|||||||
name: string;
|
name: string;
|
||||||
column: string;
|
column: string;
|
||||||
index_type: string;
|
index_type: string;
|
||||||
cardinality: number;
|
cardinality?: number | null;
|
||||||
size: number;
|
size?: number | null;
|
||||||
unique: boolean;
|
unique: boolean | number;
|
||||||
is_partial_index: boolean;
|
|
||||||
direction: string;
|
direction: string;
|
||||||
column_position: number;
|
column_position: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const IndexInfoSchema: z.ZodType<IndexInfo> = z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
table: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
column: z.string(),
|
||||||
|
index_type: z.string(),
|
||||||
|
cardinality: z.number().nullable().optional(),
|
||||||
|
size: z.number().nullable().optional(),
|
||||||
|
unique: z.union([z.boolean(), z.number()]),
|
||||||
|
direction: z.string(),
|
||||||
|
column_position: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
export type AggregatedIndexInfo = Omit<IndexInfo, 'column'> & {
|
export type AggregatedIndexInfo = Omit<IndexInfo, 'column'> & {
|
||||||
columns: { name: string; position: number }[];
|
columns: { name: string; position: number }[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface PrimaryKeyInfo {
|
export interface PrimaryKeyInfo {
|
||||||
schema: string;
|
schema: string;
|
||||||
table: string;
|
table: string;
|
||||||
column: string;
|
column: string;
|
||||||
pk_def: string;
|
pk_def: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PrimaryKeyInfoSchema: z.ZodType<PrimaryKeyInfo> = z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
table: z.string(),
|
||||||
|
column: z.string(),
|
||||||
|
pk_def: z.string(),
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface TableInfo {
|
export interface TableInfo {
|
||||||
schema: string;
|
schema: string;
|
||||||
table: string;
|
table: string;
|
||||||
rows: number;
|
rows?: number;
|
||||||
type: string;
|
type?: string;
|
||||||
engine: string;
|
engine?: string;
|
||||||
collation: string;
|
collation?: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TableInfoSchema: z.ZodType<TableInfo> = z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
table: z.string(),
|
||||||
|
rows: z.number().optional(),
|
||||||
|
type: z.string().optional(),
|
||||||
|
engine: z.string().optional(),
|
||||||
|
collation: z.string().optional(),
|
||||||
|
comment: z.string().optional(),
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface ViewInfo {
|
export interface ViewInfo {
|
||||||
schema: string;
|
schema: string;
|
||||||
view_name: string;
|
view_name: string;
|
||||||
view_definition?: string;
|
view_definition?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ViewInfoSchema: z.ZodType<ViewInfo> = z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
view_name: z.string(),
|
||||||
|
view_definition: z.string().optional(),
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ cols AS (
|
|||||||
concat('{"schema":"', col_tuple.1, '"',
|
concat('{"schema":"', col_tuple.1, '"',
|
||||||
',"table":"', col_tuple.2, '"',
|
',"table":"', col_tuple.2, '"',
|
||||||
',"name":"', col_tuple.3, '"',
|
',"name":"', col_tuple.3, '"',
|
||||||
',"ordinal_position":"', toString(col_tuple.4), '"',
|
',"ordinal_position":', toString(col_tuple.4),
|
||||||
',"type":"', col_tuple.5, '"',
|
',"type":"', col_tuple.5, '"',
|
||||||
',"nullable":"', if(col_tuple.6 = 'NULLABLE', 'true', 'false'), '"',
|
',"nullable":"', if(col_tuple.6 = 'NULLABLE', 'true', 'false'), '"',
|
||||||
',"default":"', if(col_tuple.7 = '', 'null', col_tuple.7), '"',
|
',"default":"', if(col_tuple.7 = '', 'null', col_tuple.7), '"',
|
||||||
|
|||||||
@@ -96,8 +96,7 @@ indexes_cols AS (
|
|||||||
(CASE WHEN i.indisunique = TRUE THEN 'true' ELSE 'false' END) AS is_unique,
|
(CASE WHEN i.indisunique = TRUE THEN 'true' ELSE 'false' END) AS is_unique,
|
||||||
irel.reltuples AS cardinality,
|
irel.reltuples AS cardinality,
|
||||||
1 + Array_position(i.indkey, a.attnum) AS column_position,
|
1 + Array_position(i.indkey, a.attnum) AS column_position,
|
||||||
CASE o.OPTION & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
|
CASE o.OPTION & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction
|
||||||
CASE WHEN indpred IS NOT NULL THEN 'true' ELSE 'false' END AS is_partial_index
|
|
||||||
FROM pg_index AS i
|
FROM pg_index AS i
|
||||||
JOIN pg_class AS trel ON trel.oid = i.indrelid
|
JOIN pg_class AS trel ON trel.oid = i.indrelid
|
||||||
JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
|
JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
|
||||||
@@ -114,8 +113,8 @@ cols AS (
|
|||||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', cols.table_schema::TEXT,
|
SELECT array_to_string(array_agg(CONCAT('{"schema":"', cols.table_schema::TEXT,
|
||||||
'","table":"', cols.table_name::TEXT,
|
'","table":"', cols.table_name::TEXT,
|
||||||
'","name":"', cols.column_name::TEXT,
|
'","name":"', cols.column_name::TEXT,
|
||||||
'","ordinal_position":"', cols.ordinal_position::TEXT,
|
'","ordinal_position":', cols.ordinal_position::TEXT,
|
||||||
'","type":"', LOWER(replace(cols.data_type::TEXT, '"', '')),
|
',"type":"', LOWER(replace(cols.data_type::TEXT, '"', '')),
|
||||||
'","character_maximum_length":"', COALESCE(cols.character_maximum_length::TEXT, 'null'),
|
'","character_maximum_length":"', COALESCE(cols.character_maximum_length::TEXT, 'null'),
|
||||||
'","precision":',
|
'","precision":',
|
||||||
CASE
|
CASE
|
||||||
@@ -124,7 +123,7 @@ cols AS (
|
|||||||
',"scale":', COALESCE(cols.numeric_scale::TEXT, 'null'), '}')
|
',"scale":', COALESCE(cols.numeric_scale::TEXT, 'null'), '}')
|
||||||
ELSE 'null'
|
ELSE 'null'
|
||||||
END,
|
END,
|
||||||
',"nullable":', CASE WHEN (cols.IS_NULLABLE = 'YES') THEN 'true' ELSE 'false' END::TEXT,
|
',"nullable":', CASE WHEN (cols.IS_NULLABLE = 'YES') THEN true ELSE false END::TEXT,
|
||||||
',"default":"', COALESCE(replace(replace(cols.column_default::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
|
',"default":"', COALESCE(replace(replace(cols.column_default::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
|
||||||
'","collation":"', COALESCE(cols.COLLATION_NAME::TEXT, ''),
|
'","collation":"', COALESCE(cols.COLLATION_NAME::TEXT, ''),
|
||||||
'","comment":"', COALESCE(replace(replace(dsc.description::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
|
'","comment":"', COALESCE(replace(replace(dsc.description::TEXT, '"', '\\"'), '\\x', '\\\\x'), ''),
|
||||||
@@ -146,7 +145,6 @@ cols AS (
|
|||||||
'","cardinality":', COALESCE(cardinality::TEXT, '0'),
|
'","cardinality":', COALESCE(cardinality::TEXT, '0'),
|
||||||
',"size":', COALESCE(index_size::TEXT, 'null'),
|
',"size":', COALESCE(index_size::TEXT, 'null'),
|
||||||
',"unique":', is_unique::TEXT,
|
',"unique":', is_unique::TEXT,
|
||||||
',"is_partial_index":', is_partial_index::TEXT,
|
|
||||||
',"column_position":', column_position::TEXT,
|
',"column_position":', column_position::TEXT,
|
||||||
',"direction":"', LOWER(direction::TEXT),
|
',"direction":"', LOWER(direction::TEXT),
|
||||||
'"}')), ',') AS indexes_metadata
|
'"}')), ',') AS indexes_metadata
|
||||||
@@ -178,8 +176,7 @@ cols AS (
|
|||||||
), views AS (
|
), views AS (
|
||||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', views.schemaname::TEXT,
|
SELECT array_to_string(array_agg(CONCAT('{"schema":"', views.schemaname::TEXT,
|
||||||
'","view_name":"', viewname::TEXT,
|
'","view_name":"', viewname::TEXT,
|
||||||
'","view_definition":"', encode(convert_to(REPLACE(definition::TEXT, '"', '\\"'), 'UTF8'), 'base64'),
|
'","view_definition":""}')),
|
||||||
'"}')),
|
|
||||||
',') AS views_metadata
|
',') AS views_metadata
|
||||||
FROM pg_views views
|
FROM pg_views views
|
||||||
WHERE views.schemaname NOT IN ('information_schema', 'pg_catalog')${cockroachdbViewsFilter}
|
WHERE views.schemaname NOT IN ('information_schema', 'pg_catalog')${cockroachdbViewsFilter}
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ export const mariaDBQuery = `WITH fk_info as (
|
|||||||
',"scale":', IFNULL(cols.numeric_scale, 'null'), '}')
|
',"scale":', IFNULL(cols.numeric_scale, 'null'), '}')
|
||||||
ELSE 'null'
|
ELSE 'null'
|
||||||
END,
|
END,
|
||||||
',"ordinal_position":"', cols.ordinal_position,
|
',"ordinal_position":', cols.ordinal_position,
|
||||||
'","nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
|
',"nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
|
||||||
',"default":"', IFNULL(REPLACE(REPLACE(cols.column_default, '\\\\', ''), '"', '\\"'), ''),
|
',"default":"', IFNULL(REPLACE(REPLACE(cols.column_default, '\\\\', ''), '"', '\\"'), ''),
|
||||||
'","collation":"', IFNULL(cols.collation_name, ''), '"}'
|
'","collation":"', IFNULL(cols.collation_name, ''), '"}'
|
||||||
)))))
|
)))))
|
||||||
@@ -88,7 +88,7 @@ export const mariaDBQuery = `WITH fk_info as (
|
|||||||
AND (0x00) IN (@indexes:=CONCAT_WS(',', @indexes, CONCAT('{"schema":"',indexes.table_schema,
|
AND (0x00) IN (@indexes:=CONCAT_WS(',', @indexes, CONCAT('{"schema":"',indexes.table_schema,
|
||||||
'","table":"',indexes.table_name,
|
'","table":"',indexes.table_name,
|
||||||
'","name":"', indexes.index_name,
|
'","name":"', indexes.index_name,
|
||||||
'","size":"',
|
'","size":',
|
||||||
(SELECT IFNULL(SUM(stat_value * @@innodb_page_size), -1) AS size_in_bytes
|
(SELECT IFNULL(SUM(stat_value * @@innodb_page_size), -1) AS size_in_bytes
|
||||||
FROM mysql.innodb_index_stats
|
FROM mysql.innodb_index_stats
|
||||||
WHERE stat_name = 'size'
|
WHERE stat_name = 'size'
|
||||||
@@ -96,11 +96,12 @@ export const mariaDBQuery = `WITH fk_info as (
|
|||||||
AND index_name = indexes.index_name
|
AND index_name = indexes.index_name
|
||||||
AND TABLE_NAME = indexes.table_name
|
AND TABLE_NAME = indexes.table_name
|
||||||
AND database_name = indexes.table_schema),
|
AND database_name = indexes.table_schema),
|
||||||
'","column":"', indexes.column_name,
|
',"column":"', indexes.column_name,
|
||||||
'","index_type":"', LOWER(indexes.index_type),
|
'","index_type":"', LOWER(indexes.index_type),
|
||||||
'","cardinality":', indexes.cardinality,
|
'","cardinality":', indexes.cardinality,
|
||||||
',"direction":"', (CASE WHEN indexes.collation = 'D' THEN 'desc' ELSE 'asc' END),
|
',"direction":"', (CASE WHEN indexes.collation = 'D' THEN 'desc' ELSE 'asc' END),
|
||||||
'","unique":', IF(indexes.non_unique = 1, 'false', 'true'), '}')))))
|
'","column_position":', indexes.seq_in_index,
|
||||||
|
',"unique":', IF(indexes.non_unique = 1, 'false', 'true'), '}')))))
|
||||||
), tbls as
|
), tbls as
|
||||||
(
|
(
|
||||||
(SELECT (@tbls:=NULL),
|
(SELECT (@tbls:=NULL),
|
||||||
@@ -122,7 +123,7 @@ export const mariaDBQuery = `WITH fk_info as (
|
|||||||
AND table_schema = DATABASE()
|
AND table_schema = DATABASE()
|
||||||
AND (0x00) IN (@views:=CONCAT_WS(',', @views, CONCAT('{', '"schema":"', \`TABLE_SCHEMA\`, '",',
|
AND (0x00) IN (@views:=CONCAT_WS(',', @views, CONCAT('{', '"schema":"', \`TABLE_SCHEMA\`, '",',
|
||||||
'"view_name":"', \`TABLE_NAME\`, '",',
|
'"view_name":"', \`TABLE_NAME\`, '",',
|
||||||
'"view_definition":"', REPLACE(REPLACE(TO_BASE64(VIEW_DEFINITION), ' ', ''), '\n', ''), '"}'))) ) )
|
'"view_definition":""}'))) ) )
|
||||||
)
|
)
|
||||||
(SELECT CAST(CONCAT('{"fk_info": [',IFNULL(@fk_info,''),
|
(SELECT CAST(CONCAT('{"fk_info": [',IFNULL(@fk_info,''),
|
||||||
'], "pk_info": [', IFNULL(@pk_info, ''),
|
'], "pk_info": [', IFNULL(@pk_info, ''),
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export const minimizeQuery = (query: string) => {
|
|
||||||
return query
|
|
||||||
.replace(/\s+/g, ' ') // Replace multiple spaces with a single space
|
|
||||||
.replace(/\s*;\s*/g, ';') // Remove spaces around semicolons
|
|
||||||
.trim(); // Remove leading and trailing spaces
|
|
||||||
};
|
|
||||||
@@ -84,8 +84,8 @@ export const getMySQLQuery = (
|
|||||||
',"scale":', IFNULL(cols.numeric_scale, 'null'), '}')
|
',"scale":', IFNULL(cols.numeric_scale, 'null'), '}')
|
||||||
ELSE 'null'
|
ELSE 'null'
|
||||||
END,
|
END,
|
||||||
',"ordinal_position":"', cols.ordinal_position,
|
',"ordinal_position":', cols.ordinal_position,
|
||||||
'","nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
|
',"nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
|
||||||
',"default":"', IFNULL(REPLACE(REPLACE(cols.column_default, '\\\\', ''), '"', 'ֿֿֿ\\"'), ''),
|
',"default":"', IFNULL(REPLACE(REPLACE(cols.column_default, '\\\\', ''), '"', 'ֿֿֿ\\"'), ''),
|
||||||
'","collation":"', IFNULL(cols.collation_name, ''), '"}'
|
'","collation":"', IFNULL(cols.collation_name, ''), '"}'
|
||||||
)))))
|
)))))
|
||||||
@@ -98,7 +98,7 @@ export const getMySQLQuery = (
|
|||||||
AND (0x00) IN (@indexes:=CONCAT_WS(',', @indexes, CONCAT('{"schema":"',indexes.table_schema,
|
AND (0x00) IN (@indexes:=CONCAT_WS(',', @indexes, CONCAT('{"schema":"',indexes.table_schema,
|
||||||
'","table":"',indexes.table_name,
|
'","table":"',indexes.table_name,
|
||||||
'","name":"', indexes.index_name,
|
'","name":"', indexes.index_name,
|
||||||
'","size":"',
|
'","size":',
|
||||||
(SELECT IFNULL(SUM(stat_value * @@innodb_page_size), -1) AS size_in_bytes
|
(SELECT IFNULL(SUM(stat_value * @@innodb_page_size), -1) AS size_in_bytes
|
||||||
FROM mysql.innodb_index_stats
|
FROM mysql.innodb_index_stats
|
||||||
WHERE stat_name = 'size'
|
WHERE stat_name = 'size'
|
||||||
@@ -106,7 +106,7 @@ export const getMySQLQuery = (
|
|||||||
AND index_name = indexes.index_name
|
AND index_name = indexes.index_name
|
||||||
AND TABLE_NAME = indexes.table_name
|
AND TABLE_NAME = indexes.table_name
|
||||||
AND database_name = indexes.table_schema),
|
AND database_name = indexes.table_schema),
|
||||||
'","column":"', indexes.column_name,
|
',"column":"', indexes.column_name,
|
||||||
'","index_type":"', LOWER(indexes.index_type),
|
'","index_type":"', LOWER(indexes.index_type),
|
||||||
'","cardinality":', indexes.cardinality,
|
'","cardinality":', indexes.cardinality,
|
||||||
',"direction":"', (CASE WHEN indexes.collation = 'D' THEN 'desc' ELSE 'asc' END),
|
',"direction":"', (CASE WHEN indexes.collation = 'D' THEN 'desc' ELSE 'asc' END),
|
||||||
@@ -133,7 +133,7 @@ export const getMySQLQuery = (
|
|||||||
AND table_schema = DATABASE()
|
AND table_schema = DATABASE()
|
||||||
AND (0x00) IN (@views:=CONCAT_WS(',', @views, CONCAT('{', '"schema":"', \`TABLE_SCHEMA\`, '",',
|
AND (0x00) IN (@views:=CONCAT_WS(',', @views, CONCAT('{', '"schema":"', \`TABLE_SCHEMA\`, '",',
|
||||||
'"view_name":"', \`TABLE_NAME\`, '",',
|
'"view_name":"', \`TABLE_NAME\`, '",',
|
||||||
'"view_definition":"', REPLACE(REPLACE(TO_BASE64(VIEW_DEFINITION), ' ', ''), '\n', ''), '"}'))) ) )
|
'"view_definition":""}'))) ) )
|
||||||
)
|
)
|
||||||
(SELECT CAST(CONCAT('{"fk_info": [',IFNULL(@fk_info,''),
|
(SELECT CAST(CONCAT('{"fk_info": [',IFNULL(@fk_info,''),
|
||||||
'], "pk_info": [', IFNULL(@pk_info, ''),
|
'], "pk_info": [', IFNULL(@pk_info, ''),
|
||||||
@@ -209,8 +209,8 @@ export const getMySQLQuery = (
|
|||||||
IF(cols.data_type IN ('decimal', 'numeric'),
|
IF(cols.data_type IN ('decimal', 'numeric'),
|
||||||
CONCAT('{"precision":', IFNULL(cols.numeric_precision, 'null'),
|
CONCAT('{"precision":', IFNULL(cols.numeric_precision, 'null'),
|
||||||
',"scale":', IFNULL(cols.numeric_scale, 'null'), '}'), 'null'),
|
',"scale":', IFNULL(cols.numeric_scale, 'null'), '}'), 'null'),
|
||||||
',"ordinal_position":"', cols.ordinal_position,
|
',"ordinal_position":', cols.ordinal_position,
|
||||||
'","nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
|
',"nullable":', IF(cols.is_nullable = 'YES', 'true', 'false'),
|
||||||
',"default":"', IFNULL(REPLACE(REPLACE(cols.column_default, '\\\\', ''), '"', '\\"'), ''),
|
',"default":"', IFNULL(REPLACE(REPLACE(cols.column_default, '\\\\', ''), '"', '\\"'), ''),
|
||||||
'","collation":"', IFNULL(cols.collation_name, ''), '"}')
|
'","collation":"', IFNULL(cols.collation_name, ''), '"}')
|
||||||
) FROM (
|
) FROM (
|
||||||
@@ -233,7 +233,7 @@ export const getMySQLQuery = (
|
|||||||
CONCAT('{"schema":"', cast(idx.table_schema as CHAR),
|
CONCAT('{"schema":"', cast(idx.table_schema as CHAR),
|
||||||
'","table":"', idx.table_name,
|
'","table":"', idx.table_name,
|
||||||
'","name":"', idx.index_name,
|
'","name":"', idx.index_name,
|
||||||
'","size":"', IFNULL(
|
'","size":', IFNULL(
|
||||||
(SELECT SUM(stat_value * @@innodb_page_size)
|
(SELECT SUM(stat_value * @@innodb_page_size)
|
||||||
FROM mysql.innodb_index_stats
|
FROM mysql.innodb_index_stats
|
||||||
WHERE stat_name = 'size'
|
WHERE stat_name = 'size'
|
||||||
@@ -241,7 +241,7 @@ export const getMySQLQuery = (
|
|||||||
AND index_name = idx.index_name
|
AND index_name = idx.index_name
|
||||||
AND TABLE_NAME = idx.table_name
|
AND TABLE_NAME = idx.table_name
|
||||||
AND database_name = idx.table_schema), -1),
|
AND database_name = idx.table_schema), -1),
|
||||||
'","column":"', idx.column_name,
|
',"column":"', idx.column_name,
|
||||||
'","index_type":"', LOWER(idx.index_type),
|
'","index_type":"', LOWER(idx.index_type),
|
||||||
'","cardinality":', idx.cardinality,
|
'","cardinality":', idx.cardinality,
|
||||||
',"direction":"', (CASE WHEN idx.collation = 'D' THEN 'desc' ELSE 'asc' END),
|
',"direction":"', (CASE WHEN idx.collation = 'D' THEN 'desc' ELSE 'asc' END),
|
||||||
@@ -286,7 +286,7 @@ export const getMySQLQuery = (
|
|||||||
) FROM (
|
) FROM (
|
||||||
SELECT \`TABLE_SCHEMA\`,
|
SELECT \`TABLE_SCHEMA\`,
|
||||||
\`TABLE_NAME\` AS view_name,
|
\`TABLE_NAME\` AS view_name,
|
||||||
REPLACE(REPLACE(TO_BASE64(\`VIEW_DEFINITION\`), ' ', ''), '\n', '') AS view_definition
|
null AS view_definition
|
||||||
FROM information_schema.views vws
|
FROM information_schema.views vws
|
||||||
WHERE vws.table_schema = DATABASE()
|
WHERE vws.table_schema = DATABASE()
|
||||||
) AS vws), ''),
|
) AS vws), ''),
|
||||||
|
|||||||
@@ -147,8 +147,7 @@ indexes_cols AS (
|
|||||||
(CASE WHEN i.indisunique = TRUE THEN 'true' ELSE 'false' END) AS is_unique,
|
(CASE WHEN i.indisunique = TRUE THEN 'true' ELSE 'false' END) AS is_unique,
|
||||||
irel.reltuples AS cardinality,
|
irel.reltuples AS cardinality,
|
||||||
1 + Array_position(i.indkey, a.attnum) AS column_position,
|
1 + Array_position(i.indkey, a.attnum) AS column_position,
|
||||||
CASE o.OPTION & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
|
CASE o.OPTION & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction
|
||||||
CASE WHEN indpred IS NOT NULL THEN 'true' ELSE 'false' END AS is_partial_index
|
|
||||||
FROM pg_index AS i
|
FROM pg_index AS i
|
||||||
JOIN pg_class AS trel ON trel.oid = i.indrelid
|
JOIN pg_class AS trel ON trel.oid = i.indrelid
|
||||||
JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
|
JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
|
||||||
@@ -165,8 +164,8 @@ cols AS (
|
|||||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', cols.table_schema,
|
SELECT array_to_string(array_agg(CONCAT('{"schema":"', cols.table_schema,
|
||||||
'","table":"', cols.table_name,
|
'","table":"', cols.table_name,
|
||||||
'","name":"', cols.column_name,
|
'","name":"', cols.column_name,
|
||||||
'","ordinal_position":"', cols.ordinal_position,
|
'","ordinal_position":', cols.ordinal_position,
|
||||||
'","type":"', LOWER(replace(cols.data_type, '"', '')),
|
',"type":"', LOWER(replace(cols.data_type, '"', '')),
|
||||||
'","character_maximum_length":"', COALESCE(cols.character_maximum_length::text, 'null'),
|
'","character_maximum_length":"', COALESCE(cols.character_maximum_length::text, 'null'),
|
||||||
'","precision":',
|
'","precision":',
|
||||||
CASE
|
CASE
|
||||||
@@ -203,7 +202,6 @@ cols AS (
|
|||||||
'","cardinality":', cardinality,
|
'","cardinality":', cardinality,
|
||||||
',"size":', index_size,
|
',"size":', index_size,
|
||||||
',"unique":', is_unique,
|
',"unique":', is_unique,
|
||||||
',"is_partial_index":', is_partial_index,
|
|
||||||
',"column_position":', column_position,
|
',"column_position":', column_position,
|
||||||
',"direction":"', LOWER(direction),
|
',"direction":"', LOWER(direction),
|
||||||
'"}')), ',') AS indexes_metadata
|
'"}')), ',') AS indexes_metadata
|
||||||
@@ -247,8 +245,7 @@ cols AS (
|
|||||||
), views AS (
|
), views AS (
|
||||||
SELECT array_to_string(array_agg(CONCAT('{"schema":"', views.schemaname,
|
SELECT array_to_string(array_agg(CONCAT('{"schema":"', views.schemaname,
|
||||||
'","view_name":"', viewname,
|
'","view_name":"', viewname,
|
||||||
'","view_definition":"', encode(convert_to(REPLACE(definition, '"', '\\"'), 'UTF8'), 'base64'),
|
'","view_definition":""}')),
|
||||||
'"}')),
|
|
||||||
',') AS views_metadata
|
',') AS views_metadata
|
||||||
FROM pg_views views
|
FROM pg_views views
|
||||||
WHERE views.schemaname NOT IN ('information_schema', 'pg_catalog') ${
|
WHERE views.schemaname NOT IN ('information_schema', 'pg_catalog') ${
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ WITH fk_info AS (
|
|||||||
'name', idx.name,
|
'name', idx.name,
|
||||||
'column', ic.name,
|
'column', ic.name,
|
||||||
'index_type', 'B-TREE', -- SQLite uses B-Trees for indexing
|
'index_type', 'B-TREE', -- SQLite uses B-Trees for indexing
|
||||||
'cardinality', '', -- SQLite does not provide cardinality
|
'cardinality', null, -- SQLite does not provide cardinality
|
||||||
'size', '', -- SQLite does not provide index size
|
'size', null, -- SQLite does not provide index size
|
||||||
'unique', (CASE WHEN idx."unique" = 1 THEN 'true' ELSE 'false' END),
|
'unique', (CASE WHEN idx."unique" = 1 THEN true ELSE false END),
|
||||||
'direction', '', -- SQLite does not provide direction info
|
'direction', '', -- SQLite does not provide direction info
|
||||||
'column_position', ic.seqno + 1 -- Adding 1 to convert from zero-based to one-based index
|
'column_position', ic.seqno + 1 -- Adding 1 to convert from zero-based to one-based index
|
||||||
)
|
)
|
||||||
@@ -107,12 +107,12 @@ WITH fk_info AS (
|
|||||||
CASE
|
CASE
|
||||||
WHEN instr(p.type, '(') > 0 THEN
|
WHEN instr(p.type, '(') > 0 THEN
|
||||||
json_object(
|
json_object(
|
||||||
'precision', substr(p.type, instr(p.type, '(') + 1, instr(p.type, ',') - instr(p.type, '(') - 1),
|
'precision', CAST(substr(p.type, instr(p.type, '(') + 1, instr(p.type, ',') - instr(p.type, '(') - 1) AS INTEGER),
|
||||||
'scale', substr(p.type, instr(p.type, ',') + 1, instr(p.type, ')') - instr(p.type, ',') - 1)
|
'scale', CAST(substr(p.type, instr(p.type, ',') + 1, instr(p.type, ')') - instr(p.type, ',') - 1) AS INTEGER)
|
||||||
)
|
)
|
||||||
ELSE 'null'
|
ELSE null
|
||||||
END
|
END
|
||||||
ELSE 'null'
|
ELSE null
|
||||||
END,
|
END,
|
||||||
'default', COALESCE(REPLACE(p.dflt_value, '"', '\\"'), '')
|
'default', COALESCE(REPLACE(p.dflt_value, '"', '\\"'), '')
|
||||||
)
|
)
|
||||||
@@ -231,9 +231,9 @@ WITH fk_info AS (
|
|||||||
'name', idx.name,
|
'name', idx.name,
|
||||||
'column', ic.name,
|
'column', ic.name,
|
||||||
'index_type', 'B-TREE',
|
'index_type', 'B-TREE',
|
||||||
'cardinality', '',
|
'cardinality', null,
|
||||||
'size', '',
|
'size', null,
|
||||||
'unique', (CASE WHEN idx.[unique] = 1 THEN 'true' ELSE 'false' END),
|
'unique', (CASE WHEN idx.[unique] = 1 THEN true ELSE false END),
|
||||||
'direction', '',
|
'direction', '',
|
||||||
'column_position', ic.seqno + 1
|
'column_position', ic.seqno + 1
|
||||||
)
|
)
|
||||||
@@ -280,12 +280,12 @@ WITH fk_info AS (
|
|||||||
CASE
|
CASE
|
||||||
WHEN instr(p.type, '(') > 0 THEN
|
WHEN instr(p.type, '(') > 0 THEN
|
||||||
json_object(
|
json_object(
|
||||||
'precision', substr(p.type, instr(p.type, '(') + 1, instr(p.type, ',') - instr(p.type, '(') - 1),
|
'precision', CAST(substr(p.type, instr(p.type, '(') + 1, instr(p.type, ',') - instr(p.type, '(') - 1) AS INTEGER),
|
||||||
'scale', substr(p.type, instr(p.type, ',') + 1, instr(p.type, ')') - instr(p.type, ',') - 1)
|
'scale', CAST(substr(p.type, instr(p.type, ',') + 1, instr(p.type, ')') - instr(p.type, ',') - 1) AS INTEGER)
|
||||||
)
|
)
|
||||||
ELSE 'null'
|
ELSE null
|
||||||
END
|
END
|
||||||
ELSE 'null'
|
ELSE null
|
||||||
END,
|
END,
|
||||||
'default', COALESCE(REPLACE(p.dflt_value, '"', '\\"'), '')
|
'default', COALESCE(REPLACE(p.dflt_value, '"', '\\"'), '')
|
||||||
)
|
)
|
||||||
@@ -361,7 +361,7 @@ const generateWranglerCommand = (): string => {
|
|||||||
# 5. Replace YOUR_DB_ID with your actual D1 database ID
|
# 5. Replace YOUR_DB_ID with your actual D1 database ID
|
||||||
|
|
||||||
# Step 1: Write the query to a file
|
# Step 1: Write the query to a file
|
||||||
wrangler d1 execute YOUR_DB_NAME --command $'WITH fk_info AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'column\\', fk.[from], \\'foreign_key_name\\', \\'fk_\\' || m.name || \\'_\\' || fk.[from] || \\'_\\' || fk.[table] || \\'_\\' || fk.[to], \\'reference_schema\\', \\'\\', \\'reference_table\\', fk.[table], \\'reference_column\\', fk.[to], \\'fk_def\\', \\'FOREIGN KEY (\\' || fk.[from] || \\') REFERENCES \\' || fk.[table] || \\'(\\' || fk.[to] || \\')\\' || \\' ON UPDATE \\' || fk.on_update || \\' ON DELETE \\' || fk.on_delete ) ) AS fk_metadata FROM sqlite_master m JOIN pragma_foreign_key_list(m.name) fk ON m.type = \\'table\\' WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), pk_info AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', pk.table_name, \\'field_count\\', pk.field_count, \\'column\\', pk.pk_column, \\'pk_def\\', \\'PRIMARY KEY (\\' || pk.pk_column || \\')\\' ) ) AS pk_metadata FROM ( SELECT m.name AS table_name, COUNT(p.name) AS field_count, GROUP_CONCAT(p.name) AS pk_column FROM sqlite_master m JOIN pragma_table_info(m.name) p ON m.type = \\'table\\' AND p.pk > 0 WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' GROUP BY m.name ) pk ), indexes_metadata AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'name\\', idx.name, \\'column\\', ic.name, \\'index_type\\', \\'B-TREE\\', \\'cardinality\\', \\'\\', \\'size\\', \\'\\', \\'unique\\', CASE WHEN idx.[unique] = 1 THEN \\'true\\' ELSE \\'false\\' END, \\'direction\\', \\'\\', \\'column_position\\', ic.seqno + 1 ) ) AS indexes_metadata FROM sqlite_master m JOIN pragma_index_list(m.name) idx ON m.type = \\'table\\' JOIN pragma_index_info(idx.name) ic WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), cols AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'name\\', p.name, \\'type\\', CASE WHEN INSTR(LOWER(p.type), \\'(\\') > 0 THEN SUBSTR(LOWER(p.type), 1, INSTR(LOWER(p.type), \\'(\\') - 1) ELSE LOWER(p.type) END, \\'ordinal_position\\', p.cid, \\'nullable\\', CASE WHEN p.[notnull] = 0 THEN true ELSE false END, \\'collation\\', \\'\\', \\'character_maximum_length\\', CASE WHEN LOWER(p.type) LIKE \\'char%\\' OR LOWER(p.type) LIKE \\'varchar%\\' THEN CASE WHEN INSTR(p.type, \\'(\\') > 0 THEN REPLACE( SUBSTR(p.type, INSTR(p.type, \\'(\\') + 1, LENGTH(p.type) - INSTR(p.type, \\'(\\') - 1), \\')\\', \\'\\' ) ELSE \\'null\\' END ELSE \\'null\\' END, \\'precision\\', CASE WHEN LOWER(p.type) LIKE \\'decimal%\\' OR LOWER(p.type) LIKE \\'numeric%\\' THEN CASE WHEN instr(p.type, \\'(\\') > 0 THEN json_object( \\'precision\\', substr(p.type, instr(p.type, \\'(\\') + 1, instr(p.type, \\',\\') - instr(p.type, \\'(\\') - 1), \\'scale\\', substr(p.type, instr(p.type, \\',\\') + 1, instr(p.type, \\')\\') - instr(p.type, \\',\\') - 1) ) ELSE \\'null\\' END ELSE \\'null\\' END, \\'default\\', COALESCE(REPLACE(p.dflt_value, \\'"\\', \\'\\\\\\"\\'), \\'\\') ) ) AS cols_metadata FROM sqlite_master m JOIN pragma_table_info(m.name) p ON m.type in (\\'table\\', \\'view\\') WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), tbls AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'rows\\', -1, \\'type\\', \\'table\\', \\'engine\\', \\'\\', \\'collation\\', \\'\\' ) ) AS tbls_metadata FROM sqlite_master m WHERE m.type in (\\'table\\', \\'view\\') AND m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), views AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'view_name\\', m.name ) ) AS views_metadata FROM sqlite_master m WHERE m.type = \\'view\\' AND m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ) SELECT json_object( \\'fk_info\\', json((SELECT fk_metadata FROM fk_info)), \\'pk_info\\', json((SELECT pk_metadata FROM pk_info)), \\'columns\\', json((SELECT cols_metadata FROM cols)), \\'indexes\\', json((SELECT indexes_metadata FROM indexes_metadata)), \\'tables\\', json((SELECT tbls_metadata FROM tbls)), \\'views\\', json((SELECT views_metadata FROM views)), \\'database_name\\', \\'sqlite\\', \\'version\\', \\'\\' ) AS metadata_json_to_import;' --remote
|
wrangler d1 execute YOUR_DB_NAME --command $'WITH fk_info AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'column\\', fk.[from], \\'foreign_key_name\\', \\'fk_\\' || m.name || \\'_\\' || fk.[from] || \\'_\\' || fk.[table] || \\'_\\' || fk.[to], \\'reference_schema\\', \\'\\', \\'reference_table\\', fk.[table], \\'reference_column\\', fk.[to], \\'fk_def\\', \\'FOREIGN KEY (\\' || fk.[from] || \\') REFERENCES \\' || fk.[table] || \\'(\\' || fk.[to] || \\')\\' || \\' ON UPDATE \\' || fk.on_update || \\' ON DELETE \\' || fk.on_delete ) ) AS fk_metadata FROM sqlite_master m JOIN pragma_foreign_key_list(m.name) fk ON m.type = \\'table\\' WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), pk_info AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', pk.table_name, \\'field_count\\', pk.field_count, \\'column\\', pk.pk_column, \\'pk_def\\', \\'PRIMARY KEY (\\' || pk.pk_column || \\')\\' ) ) AS pk_metadata FROM ( SELECT m.name AS table_name, COUNT(p.name) AS field_count, GROUP_CONCAT(p.name) AS pk_column FROM sqlite_master m JOIN pragma_table_info(m.name) p ON m.type = \\'table\\' AND p.pk > 0 WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' GROUP BY m.name ) pk ), indexes_metadata AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'name\\', idx.name, \\'column\\', ic.name, \\'index_type\\', \\'B-TREE\\', \\'cardinality\\', \\'\\', \\'size\\', null, \\'unique\\', CASE WHEN idx.[unique] = 1 THEN true ELSE false END, \\'direction\\', \\'\\', \\'column_position\\', ic.seqno + 1 ) ) AS indexes_metadata FROM sqlite_master m JOIN pragma_index_list(m.name) idx ON m.type = \\'table\\' JOIN pragma_index_info(idx.name) ic WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), cols AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'name\\', p.name, \\'type\\', CASE WHEN INSTR(LOWER(p.type), \\'(\\') > 0 THEN SUBSTR(LOWER(p.type), 1, INSTR(LOWER(p.type), \\'(\\') - 1) ELSE LOWER(p.type) END, \\'ordinal_position\\', p.cid, \\'nullable\\', CASE WHEN p.[notnull] = 0 THEN true ELSE false END, \\'collation\\', \\'\\', \\'character_maximum_length\\', CASE WHEN LOWER(p.type) LIKE \\'char%\\' OR LOWER(p.type) LIKE \\'varchar%\\' THEN CASE WHEN INSTR(p.type, \\'(\\') > 0 THEN REPLACE( SUBSTR(p.type, INSTR(p.type, \\'(\\') + 1, LENGTH(p.type) - INSTR(p.type, \\'(\\') - 1), \\')\\', \\'\\' ) ELSE \\'null\\' END ELSE \\'null\\' END, \\'precision\\', CASE WHEN LOWER(p.type) LIKE \\'decimal%\\' OR LOWER(p.type) LIKE \\'numeric%\\' THEN CASE WHEN instr(p.type, \\'(\\') > 0 THEN json_object( \\'precision\\', CAST(substr(p.type, instr(p.type, \\'(\\') + 1, instr(p.type, \\',\\') - instr(p.type, \\'(\\') - 1) as INTIGER), \\'scale\\', CAST(substr(p.type, instr(p.type, \\',\\') + 1, instr(p.type, \\')\\') - instr(p.type, \\',\\') - 1) AS INTIGER) ) ELSE null END ELSE null END, \\'default\\', COALESCE(REPLACE(p.dflt_value, \\'"\\', \\'\\\\\\"\\'), \\'\\') ) ) AS cols_metadata FROM sqlite_master m JOIN pragma_table_info(m.name) p ON m.type in (\\'table\\', \\'view\\') WHERE m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), tbls AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'table\\', m.name, \\'rows\\', -1, \\'type\\', \\'table\\', \\'engine\\', \\'\\', \\'collation\\', \\'\\' ) ) AS tbls_metadata FROM sqlite_master m WHERE m.type in (\\'table\\', \\'view\\') AND m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ), views AS ( SELECT json_group_array( json_object( \\'schema\\', \\'\\', \\'view_name\\', m.name ) ) AS views_metadata FROM sqlite_master m WHERE m.type = \\'view\\' AND m.name NOT LIKE \\'\\\\_cf\\\\_%\\' ESCAPE \\'\\\\\\' ) SELECT json_object( \\'fk_info\\', json((SELECT fk_metadata FROM fk_info)), \\'pk_info\\', json((SELECT pk_metadata FROM pk_info)), \\'columns\\', json((SELECT cols_metadata FROM cols)), \\'indexes\\', json((SELECT indexes_metadata FROM indexes_metadata)), \\'tables\\', json((SELECT tbls_metadata FROM tbls)), \\'views\\', json((SELECT views_metadata FROM views)), \\'database_name\\', \\'sqlite\\', \\'version\\', \\'\\' ) AS metadata_json_to_import;' --remote
|
||||||
|
|
||||||
# Step 2: Copy the output of the command above and paste it into app.chartdb.io
|
# Step 2: Copy the output of the command above and paste it into app.chartdb.io
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -68,12 +68,12 @@ cols AS (
|
|||||||
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), ''), 'json') +
|
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), ''), 'json') +
|
||||||
'", "ordinal_position": ' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
'", "ordinal_position": ' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
||||||
', "type": "' + STRING_ESCAPE(LOWER(cols.DATA_TYPE), 'json') +
|
', "type": "' + STRING_ESCAPE(LOWER(cols.DATA_TYPE), 'json') +
|
||||||
'", "character_maximum_length": ' +
|
'", "character_maximum_length": "' +
|
||||||
CASE
|
CASE
|
||||||
WHEN cols.CHARACTER_MAXIMUM_LENGTH IS NULL THEN 'null'
|
WHEN cols.CHARACTER_MAXIMUM_LENGTH IS NULL THEN 'null'
|
||||||
ELSE CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX))
|
ELSE CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX))
|
||||||
END +
|
END +
|
||||||
', "precision": ' +
|
'", "precision": ' +
|
||||||
CASE
|
CASE
|
||||||
WHEN cols.DATA_TYPE IN ('numeric', 'decimal')
|
WHEN cols.DATA_TYPE IN ('numeric', 'decimal')
|
||||||
THEN '{"precision":' + COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null') +
|
THEN '{"precision":' + COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null') +
|
||||||
@@ -166,15 +166,7 @@ views AS (
|
|||||||
JSON_QUERY(N'{
|
JSON_QUERY(N'{
|
||||||
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
||||||
'", "view_name": "' + STRING_ESCAPE(COALESCE(REPLACE(v.name, '"', ''), ''), 'json') +
|
'", "view_name": "' + STRING_ESCAPE(COALESCE(REPLACE(v.name, '"', ''), ''), 'json') +
|
||||||
'", "view_definition": "' +
|
'", "view_definition": ""}') COLLATE DATABASE_DEFAULT
|
||||||
STRING_ESCAPE(
|
|
||||||
CAST(
|
|
||||||
'' AS XML
|
|
||||||
).value(
|
|
||||||
'xs:base64Binary(sql:column("DefinitionBinary"))',
|
|
||||||
'VARCHAR(MAX)'
|
|
||||||
), 'json') +
|
|
||||||
N'"}') COLLATE DATABASE_DEFAULT
|
|
||||||
), N','
|
), N','
|
||||||
) + N']' AS all_views_json
|
) + N']' AS all_views_json
|
||||||
FROM sys.views v
|
FROM sys.views v
|
||||||
@@ -270,12 +262,12 @@ cols AS (
|
|||||||
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), ''), 'json') +
|
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), ''), 'json') +
|
||||||
'", "ordinal_position": ' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
'", "ordinal_position": ' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
||||||
', "type": "' + STRING_ESCAPE(LOWER(cols.DATA_TYPE), 'json') +
|
', "type": "' + STRING_ESCAPE(LOWER(cols.DATA_TYPE), 'json') +
|
||||||
'", "character_maximum_length": ' +
|
'", "character_maximum_length": "' +
|
||||||
CASE
|
CASE
|
||||||
WHEN cols.CHARACTER_MAXIMUM_LENGTH IS NULL THEN 'null'
|
WHEN cols.CHARACTER_MAXIMUM_LENGTH IS NULL THEN 'null'
|
||||||
ELSE CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX))
|
ELSE CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX))
|
||||||
END +
|
END +
|
||||||
', "precision": ' +
|
'", "precision": ' +
|
||||||
CASE
|
CASE
|
||||||
WHEN cols.DATA_TYPE IN ('numeric', 'decimal')
|
WHEN cols.DATA_TYPE IN ('numeric', 'decimal')
|
||||||
THEN '{"precision":' + COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null') +
|
THEN '{"precision":' + COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null') +
|
||||||
@@ -385,12 +377,7 @@ views AS (
|
|||||||
N'{
|
N'{
|
||||||
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
||||||
'", "view_name": "' + STRING_ESCAPE(COALESCE(REPLACE(v.name, '"', ''), ''), 'json') +
|
'", "view_name": "' + STRING_ESCAPE(COALESCE(REPLACE(v.name, '"', ''), ''), 'json') +
|
||||||
'", "view_definition": "' +
|
'", "view_definition": ""}'
|
||||||
CAST(
|
|
||||||
(
|
|
||||||
SELECT CAST(OBJECT_DEFINITION(v.object_id) AS VARBINARY(MAX)) FOR XML PATH('')
|
|
||||||
) AS NVARCHAR(MAX)
|
|
||||||
) + N'"}'
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
FROM
|
FROM
|
||||||
|
|||||||
@@ -6,6 +6,18 @@ export const fixMetadataJson = async (
|
|||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
await waitFor(1000);
|
await waitFor(1000);
|
||||||
|
|
||||||
|
// Replace problematic array default values with null
|
||||||
|
metadataJson = metadataJson.replace(
|
||||||
|
/"default": "?'?\[[^\]]*\]'?"?(\\")?(,|\})/gs,
|
||||||
|
'"default": null$2'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generic fix for all default values with '\ pattern - convert to just '
|
||||||
|
metadataJson = metadataJson.replace(
|
||||||
|
/"default":\s*"(.*?)'\\"(,|\})/g,
|
||||||
|
'"default": "$1"$2'
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: remove this temporary eslint disable
|
// TODO: remove this temporary eslint disable
|
||||||
return (
|
return (
|
||||||
metadataJson
|
metadataJson
|
||||||
@@ -24,6 +36,26 @@ export const fixMetadataJson = async (
|
|||||||
.replace(/"""([^",}]+)"""/g, '"$1"') // Remove tripple quotes from keys
|
.replace(/"""([^",}]+)"""/g, '"$1"') // Remove tripple quotes from keys
|
||||||
.replace(/""([^",}]+)""/g, '"$1"') // Remove double quotes from keys
|
.replace(/""([^",}]+)""/g, '"$1"') // Remove double quotes from keys
|
||||||
|
|
||||||
|
.replace(/'"([^"]+)"'/g, '\\"$1\\"') // Replace single-quoted double quotes
|
||||||
|
.replace(/'(".*?")'/g, "'\\$1'") // Handle cases like '"{}"'::json
|
||||||
|
|
||||||
|
// Handle specific case for nextval with quoted identifiers
|
||||||
|
.replace(
|
||||||
|
/nextval\('(".*?")'::regclass\)/g,
|
||||||
|
"nextval('\\$1'::regclass)"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle cases like "'CHAT'::"CustomType"" (ensures existing quotes are escaped for JSON)
|
||||||
|
/* eslint-disable-next-line no-useless-escape */
|
||||||
|
.replace(/'([^']+)'::\"([^\"]+)\"/g, '\'$1\'::\\\"$2\\\"')
|
||||||
|
|
||||||
|
// Convert string "null" to actual null for precision field
|
||||||
|
.replace(/"precision": "null"/g, '"precision": null')
|
||||||
|
|
||||||
|
// Convert string "true"/"false" to actual boolean for nullable field
|
||||||
|
.replace(/"nullable": "false"/g, '"nullable": false')
|
||||||
|
.replace(/"nullable": "true"/g, '"nullable": true')
|
||||||
|
|
||||||
/* eslint-disable-next-line no-useless-escape */
|
/* eslint-disable-next-line no-useless-escape */
|
||||||
.replace(/\"/g, '___ESCAPED_QUOTE___') // Temporarily replace empty strings
|
.replace(/\"/g, '___ESCAPED_QUOTE___') // Temporarily replace empty strings
|
||||||
.replace(/(?<=:\s*)""(?=\s*[,}])/g, '___EMPTY___') // Temporarily replace empty strings
|
.replace(/(?<=:\s*)""(?=\s*[,}])/g, '___EMPTY___') // Temporarily replace empty strings
|
||||||
@@ -46,3 +78,13 @@ export const isStringMetadataJson = (metadataJsonString: string): boolean => {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const minimizeQuery = (query: string) => {
|
||||||
|
if (!query) return '';
|
||||||
|
|
||||||
|
// Split into lines, trim leading spaces from each line, then rejoin
|
||||||
|
return query
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.replace(/^\s+/, '')) // Remove only leading spaces
|
||||||
|
.join('\n');
|
||||||
|
};
|
||||||
|
|||||||
712
src/lib/data/sql-import/common.ts
Normal file
@@ -0,0 +1,712 @@
|
|||||||
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
|
import { generateDiagramId, generateId } from '@/lib/utils';
|
||||||
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
|
import type { Cardinality, DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
|
import type { DBField } from '@/lib/domain/db-field';
|
||||||
|
import type { DataType } from '@/lib/data/data-types/data-types';
|
||||||
|
import { genericDataTypes } from '@/lib/data/data-types/generic-data-types';
|
||||||
|
import { randomColor } from '@/lib/colors';
|
||||||
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
|
|
||||||
|
// Common interfaces for SQL entities
|
||||||
|
export interface SQLColumn {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
nullable: boolean;
|
||||||
|
primaryKey: boolean;
|
||||||
|
unique: boolean;
|
||||||
|
typeArgs?: {
|
||||||
|
length?: number;
|
||||||
|
precision?: number;
|
||||||
|
scale?: number;
|
||||||
|
};
|
||||||
|
comment?: string;
|
||||||
|
default?: string;
|
||||||
|
increment?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLTable {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
schema?: string;
|
||||||
|
columns: SQLColumn[];
|
||||||
|
indexes: SQLIndex[];
|
||||||
|
comment?: string;
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLIndex {
|
||||||
|
name: string;
|
||||||
|
columns: string[];
|
||||||
|
unique: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLForeignKey {
|
||||||
|
name: string;
|
||||||
|
sourceTable: string;
|
||||||
|
sourceSchema?: string;
|
||||||
|
sourceColumn: string;
|
||||||
|
targetTable: string;
|
||||||
|
targetSchema?: string;
|
||||||
|
targetColumn: string;
|
||||||
|
sourceTableId: string;
|
||||||
|
targetTableId: string;
|
||||||
|
updateAction?: string;
|
||||||
|
deleteAction?: string;
|
||||||
|
sourceCardinality?: Cardinality;
|
||||||
|
targetCardinality?: Cardinality;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLParserResult {
|
||||||
|
tables: SQLTable[];
|
||||||
|
relationships: SQLForeignKey[];
|
||||||
|
types?: SQLCustomType[];
|
||||||
|
enums?: SQLEnumType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define more specific types for SQL AST nodes
|
||||||
|
export interface SQLASTNode {
|
||||||
|
type: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLBinaryExpr extends SQLASTNode {
|
||||||
|
type: 'binary_expr';
|
||||||
|
left: SQLASTNode;
|
||||||
|
right: SQLASTNode;
|
||||||
|
operator: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLFunctionNode extends SQLASTNode {
|
||||||
|
type: 'function';
|
||||||
|
name: string;
|
||||||
|
args?: {
|
||||||
|
value: SQLASTArg[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLColumnRef extends SQLASTNode {
|
||||||
|
type: 'column_ref';
|
||||||
|
column: string;
|
||||||
|
table?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLExprList extends SQLASTNode {
|
||||||
|
type: 'expr_list';
|
||||||
|
value: Array<{ value: string | number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLStringLiteral extends SQLASTNode {
|
||||||
|
type: 'single_quote_string' | 'double_quote_string';
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SQLASTArg =
|
||||||
|
| SQLColumnRef
|
||||||
|
| SQLStringLiteral
|
||||||
|
| { type: string; value: string | number };
|
||||||
|
|
||||||
|
export interface SQLCustomType {
|
||||||
|
name: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SQLEnumType {
|
||||||
|
name: string;
|
||||||
|
values: string[];
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for SQL dialect handling
|
||||||
|
export function quoteIdentifier(str: string, dbType: DatabaseType): string {
|
||||||
|
switch (dbType) {
|
||||||
|
case DatabaseType.MYSQL:
|
||||||
|
case DatabaseType.MARIADB:
|
||||||
|
return `\`${str}\``;
|
||||||
|
case DatabaseType.POSTGRESQL:
|
||||||
|
case DatabaseType.SQLITE:
|
||||||
|
return `"${str}"`;
|
||||||
|
case DatabaseType.SQL_SERVER:
|
||||||
|
return `[${str}]`;
|
||||||
|
default:
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSQLFromAST(
|
||||||
|
ast: SQLASTNode | null | undefined,
|
||||||
|
dbType: DatabaseType = DatabaseType.GENERIC
|
||||||
|
): string {
|
||||||
|
if (!ast) return '';
|
||||||
|
|
||||||
|
if (ast.type === 'binary_expr') {
|
||||||
|
const expr = ast as SQLBinaryExpr;
|
||||||
|
const leftSQL = buildSQLFromAST(expr.left, dbType);
|
||||||
|
const rightSQL = buildSQLFromAST(expr.right, dbType);
|
||||||
|
return `${leftSQL} ${expr.operator} ${rightSQL}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ast.type === 'function') {
|
||||||
|
const func = ast as SQLFunctionNode;
|
||||||
|
let expr = func.name;
|
||||||
|
if (func.args) {
|
||||||
|
expr +=
|
||||||
|
'(' +
|
||||||
|
func.args.value
|
||||||
|
.map((v: SQLASTArg) => {
|
||||||
|
if (v.type === 'column_ref')
|
||||||
|
return quoteIdentifier(
|
||||||
|
(v as SQLColumnRef).column,
|
||||||
|
dbType
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
v.type === 'single_quote_string' ||
|
||||||
|
v.type === 'double_quote_string'
|
||||||
|
)
|
||||||
|
return "'" + (v as SQLStringLiteral).value + "'";
|
||||||
|
return v.value;
|
||||||
|
})
|
||||||
|
.join(', ') +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
} else if (ast.type === 'column_ref') {
|
||||||
|
return quoteIdentifier((ast as SQLColumnRef).column, dbType);
|
||||||
|
} else if (ast.type === 'expr_list') {
|
||||||
|
return (ast as SQLExprList).value.map((v) => v.value).join(' AND ');
|
||||||
|
} else {
|
||||||
|
const valueNode = ast as { type: string; value: string | number };
|
||||||
|
return typeof valueNode.value === 'string'
|
||||||
|
? "'" + valueNode.value + "'"
|
||||||
|
: String(valueNode.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to determine cardinality of relationships
|
||||||
|
export function determineCardinality(
|
||||||
|
isSourceUnique: boolean,
|
||||||
|
isTargetUnique: boolean
|
||||||
|
): { sourceCardinality: Cardinality; targetCardinality: Cardinality } {
|
||||||
|
if (isSourceUnique && isTargetUnique) {
|
||||||
|
return {
|
||||||
|
sourceCardinality: 'one',
|
||||||
|
targetCardinality: 'one',
|
||||||
|
};
|
||||||
|
} else if (isSourceUnique) {
|
||||||
|
return {
|
||||||
|
sourceCardinality: 'one',
|
||||||
|
targetCardinality: 'many',
|
||||||
|
};
|
||||||
|
} else if (isTargetUnique) {
|
||||||
|
return {
|
||||||
|
sourceCardinality: 'many',
|
||||||
|
targetCardinality: 'one',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
sourceCardinality: 'many',
|
||||||
|
targetCardinality: 'many',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map SQL data type to generic data type in our system
|
||||||
|
export function mapSQLTypeToGenericType(
|
||||||
|
sqlType: string,
|
||||||
|
databaseType?: DatabaseType
|
||||||
|
): DataType {
|
||||||
|
if (!sqlType) {
|
||||||
|
return genericDataTypes.find((t) => t.id === 'text')!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the SQL type to lowercase for consistency
|
||||||
|
const normalizedSqlType = sqlType.toLowerCase();
|
||||||
|
|
||||||
|
// Add special case handling for SQLite INTEGER type
|
||||||
|
if (
|
||||||
|
databaseType === DatabaseType.SQLITE &&
|
||||||
|
(normalizedSqlType === 'integer' || normalizedSqlType === 'int')
|
||||||
|
) {
|
||||||
|
return genericDataTypes.find((t) => t.id === 'integer')!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dialect-specific type mappings
|
||||||
|
const dialectAffinity =
|
||||||
|
(databaseType && typeAffinity[databaseType]) ||
|
||||||
|
typeAffinity[DatabaseType.GENERIC];
|
||||||
|
|
||||||
|
// Handle specific database dialect mappings
|
||||||
|
if (databaseType) {
|
||||||
|
// Try to find a mapping for the normalized type
|
||||||
|
const typeMapping = dialectAffinity[normalizedSqlType];
|
||||||
|
if (typeMapping) {
|
||||||
|
const foundType = genericDataTypes.find(
|
||||||
|
(t) => t.id === typeMapping
|
||||||
|
);
|
||||||
|
if (foundType) return foundType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try direct mapping by normalizing the input type
|
||||||
|
const normalizedType = normalizedSqlType.replace(/\(.*\)/, '');
|
||||||
|
const matchedType = genericDataTypes.find((t) => t.id === normalizedType);
|
||||||
|
if (matchedType) return matchedType;
|
||||||
|
|
||||||
|
// Generic type mappings as a fallback
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
int: 'integer',
|
||||||
|
integer: 'integer',
|
||||||
|
smallint: 'smallint',
|
||||||
|
bigint: 'bigint',
|
||||||
|
decimal: 'decimal',
|
||||||
|
numeric: 'numeric',
|
||||||
|
float: 'float',
|
||||||
|
double: 'double',
|
||||||
|
varchar: 'varchar',
|
||||||
|
'character varying': 'varchar',
|
||||||
|
char: 'char',
|
||||||
|
character: 'char',
|
||||||
|
text: 'text',
|
||||||
|
boolean: 'boolean',
|
||||||
|
bool: 'boolean',
|
||||||
|
timestamp: 'timestamp',
|
||||||
|
datetime: 'timestamp',
|
||||||
|
date: 'date',
|
||||||
|
time: 'time',
|
||||||
|
json: 'json',
|
||||||
|
jsonb: 'json',
|
||||||
|
real: 'real',
|
||||||
|
blob: 'blob',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mappedType = typeMap[normalizedType];
|
||||||
|
if (mappedType) {
|
||||||
|
const foundType = genericDataTypes.find((t) => t.id === mappedType);
|
||||||
|
if (foundType) return foundType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to text as last resort
|
||||||
|
return genericDataTypes.find((t) => t.id === 'text')!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type affinity definitions for different database dialects
|
||||||
|
export const typeAffinity: Record<string, Record<string, string>> = {
|
||||||
|
[DatabaseType.POSTGRESQL]: {
|
||||||
|
// PostgreSQL data types (all lowercase for consistency)
|
||||||
|
int: 'integer',
|
||||||
|
integer: 'integer',
|
||||||
|
int4: 'integer',
|
||||||
|
smallint: 'smallint',
|
||||||
|
int2: 'smallint',
|
||||||
|
bigint: 'bigint',
|
||||||
|
int8: 'bigint',
|
||||||
|
decimal: 'decimal',
|
||||||
|
numeric: 'numeric',
|
||||||
|
real: 'real',
|
||||||
|
'double precision': 'double',
|
||||||
|
float: 'float',
|
||||||
|
float4: 'float',
|
||||||
|
float8: 'double',
|
||||||
|
boolean: 'boolean',
|
||||||
|
bool: 'boolean',
|
||||||
|
varchar: 'varchar',
|
||||||
|
'character varying': 'varchar',
|
||||||
|
char: 'char',
|
||||||
|
character: 'char',
|
||||||
|
text: 'text',
|
||||||
|
date: 'date',
|
||||||
|
timestamp: 'timestamp',
|
||||||
|
time: 'time',
|
||||||
|
json: 'json',
|
||||||
|
jsonb: 'jsonb',
|
||||||
|
},
|
||||||
|
[DatabaseType.MYSQL]: {
|
||||||
|
// MySQL data types (all lowercase for consistency)
|
||||||
|
int: 'integer',
|
||||||
|
integer: 'integer',
|
||||||
|
smallint: 'smallint',
|
||||||
|
tinyint: 'tinyint',
|
||||||
|
bigint: 'bigint',
|
||||||
|
decimal: 'decimal',
|
||||||
|
numeric: 'numeric',
|
||||||
|
float: 'float',
|
||||||
|
double: 'double',
|
||||||
|
boolean: 'tinyint',
|
||||||
|
bool: 'tinyint',
|
||||||
|
varchar: 'varchar',
|
||||||
|
char: 'char',
|
||||||
|
text: 'text',
|
||||||
|
date: 'date',
|
||||||
|
datetime: 'datetime',
|
||||||
|
timestamp: 'timestamp',
|
||||||
|
time: 'time',
|
||||||
|
json: 'json',
|
||||||
|
},
|
||||||
|
[DatabaseType.MARIADB]: {
|
||||||
|
// MariaDB data types (all lowercase for consistency)
|
||||||
|
int: 'integer',
|
||||||
|
integer: 'integer',
|
||||||
|
smallint: 'smallint',
|
||||||
|
tinyint: 'tinyint',
|
||||||
|
bigint: 'bigint',
|
||||||
|
decimal: 'decimal',
|
||||||
|
numeric: 'numeric',
|
||||||
|
float: 'float',
|
||||||
|
double: 'double',
|
||||||
|
boolean: 'tinyint',
|
||||||
|
bool: 'tinyint',
|
||||||
|
varchar: 'varchar',
|
||||||
|
char: 'char',
|
||||||
|
text: 'text',
|
||||||
|
date: 'date',
|
||||||
|
datetime: 'datetime',
|
||||||
|
timestamp: 'timestamp',
|
||||||
|
time: 'time',
|
||||||
|
json: 'json',
|
||||||
|
},
|
||||||
|
[DatabaseType.SQL_SERVER]: {
|
||||||
|
// SQL Server data types (all lowercase for consistency)
|
||||||
|
int: 'integer',
|
||||||
|
integer: 'integer',
|
||||||
|
smallint: 'smallint',
|
||||||
|
bigint: 'bigint',
|
||||||
|
decimal: 'decimal',
|
||||||
|
numeric: 'numeric',
|
||||||
|
float: 'float',
|
||||||
|
real: 'real',
|
||||||
|
bit: 'bit',
|
||||||
|
boolean: 'bit',
|
||||||
|
bool: 'bit',
|
||||||
|
varchar: 'varchar',
|
||||||
|
nvarchar: 'nvarchar',
|
||||||
|
char: 'char',
|
||||||
|
nchar: 'nchar',
|
||||||
|
text: 'text',
|
||||||
|
ntext: 'ntext',
|
||||||
|
date: 'date',
|
||||||
|
datetime: 'datetime',
|
||||||
|
datetime2: 'datetime2',
|
||||||
|
time: 'time',
|
||||||
|
uniqueidentifier: 'uniqueidentifier',
|
||||||
|
},
|
||||||
|
[DatabaseType.SQLITE]: {
|
||||||
|
// SQLite storage classes (all lowercase for consistency)
|
||||||
|
integer: 'integer',
|
||||||
|
int: 'integer',
|
||||||
|
bigint: 'bigint',
|
||||||
|
smallint: 'smallint',
|
||||||
|
tinyint: 'tinyint',
|
||||||
|
real: 'real',
|
||||||
|
float: 'real',
|
||||||
|
double: 'real',
|
||||||
|
numeric: 'real',
|
||||||
|
decimal: 'real',
|
||||||
|
text: 'text',
|
||||||
|
varchar: 'text',
|
||||||
|
char: 'text',
|
||||||
|
blob: 'blob',
|
||||||
|
binary: 'blob',
|
||||||
|
varbinary: 'blob',
|
||||||
|
timestamp: 'timestamp',
|
||||||
|
datetime: 'timestamp',
|
||||||
|
date: 'date',
|
||||||
|
boolean: 'integer',
|
||||||
|
bool: 'integer',
|
||||||
|
time: 'text',
|
||||||
|
json: 'text',
|
||||||
|
},
|
||||||
|
[DatabaseType.GENERIC]: {
|
||||||
|
// Generic fallback types (all lowercase for consistency)
|
||||||
|
integer: 'integer',
|
||||||
|
int: 'integer',
|
||||||
|
smallint: 'smallint',
|
||||||
|
bigint: 'bigint',
|
||||||
|
decimal: 'decimal',
|
||||||
|
numeric: 'numeric',
|
||||||
|
float: 'float',
|
||||||
|
double: 'double',
|
||||||
|
real: 'real',
|
||||||
|
boolean: 'boolean',
|
||||||
|
bool: 'boolean',
|
||||||
|
varchar: 'varchar',
|
||||||
|
'character varying': 'varchar',
|
||||||
|
char: 'char',
|
||||||
|
character: 'char',
|
||||||
|
text: 'text',
|
||||||
|
date: 'date',
|
||||||
|
timestamp: 'timestamp',
|
||||||
|
datetime: 'timestamp',
|
||||||
|
time: 'time',
|
||||||
|
json: 'json',
|
||||||
|
jsonb: 'json',
|
||||||
|
blob: 'blob',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert SQLParserResult to ChartDB Diagram structure
|
||||||
|
export function convertToChartDBDiagram(
|
||||||
|
parserResult: SQLParserResult,
|
||||||
|
sourceDatabaseType: DatabaseType,
|
||||||
|
targetDatabaseType: DatabaseType
|
||||||
|
): Diagram {
|
||||||
|
// Create a mapping of old table IDs to new ones
|
||||||
|
const tableIdMapping = new Map<string, string>();
|
||||||
|
|
||||||
|
// Convert SQL tables to ChartDB tables
|
||||||
|
const tables: DBTable[] = parserResult.tables.map((table, index) => {
|
||||||
|
const row = Math.floor(index / 4);
|
||||||
|
const col = index % 4;
|
||||||
|
const tableSpacing = 300;
|
||||||
|
const newId = generateId();
|
||||||
|
tableIdMapping.set(table.id, newId);
|
||||||
|
|
||||||
|
// Create fields from columns
|
||||||
|
const fields: DBField[] = table.columns.map((column) => {
|
||||||
|
// Use special case handling for specific database types to ensure correct mapping
|
||||||
|
let mappedType: DataType;
|
||||||
|
|
||||||
|
// SQLite-specific handling for numeric types
|
||||||
|
if (sourceDatabaseType === DatabaseType.SQLITE) {
|
||||||
|
const normalizedType = column.type.toLowerCase();
|
||||||
|
|
||||||
|
if (normalizedType === 'integer' || normalizedType === 'int') {
|
||||||
|
// Ensure integer types are preserved
|
||||||
|
mappedType = { id: 'integer', name: 'integer' };
|
||||||
|
} else if (
|
||||||
|
normalizedType === 'real' ||
|
||||||
|
normalizedType === 'float' ||
|
||||||
|
normalizedType === 'double' ||
|
||||||
|
normalizedType === 'numeric' ||
|
||||||
|
normalizedType === 'decimal'
|
||||||
|
) {
|
||||||
|
// Ensure real types are preserved
|
||||||
|
mappedType = { id: 'real', name: 'real' };
|
||||||
|
} else if (normalizedType === 'blob') {
|
||||||
|
// Ensure blob types are preserved
|
||||||
|
mappedType = { id: 'blob', name: 'blob' };
|
||||||
|
} else {
|
||||||
|
// Use the standard mapping for other types
|
||||||
|
mappedType = mapSQLTypeToGenericType(
|
||||||
|
column.type,
|
||||||
|
sourceDatabaseType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle MySQL/MariaDB integer types specifically
|
||||||
|
else if (
|
||||||
|
sourceDatabaseType === DatabaseType.MYSQL ||
|
||||||
|
sourceDatabaseType === DatabaseType.MARIADB
|
||||||
|
) {
|
||||||
|
const normalizedType = column.type
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\(\d+\)/, '')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Handle various integer types
|
||||||
|
if (normalizedType === 'tinyint') {
|
||||||
|
mappedType = { id: 'tinyint', name: 'tinyint' };
|
||||||
|
} else if (
|
||||||
|
normalizedType === 'int' ||
|
||||||
|
normalizedType === 'integer'
|
||||||
|
) {
|
||||||
|
mappedType = { id: 'int', name: 'int' };
|
||||||
|
} else if (normalizedType === 'smallint') {
|
||||||
|
mappedType = { id: 'smallint', name: 'smallint' };
|
||||||
|
} else if (normalizedType === 'mediumint') {
|
||||||
|
mappedType = { id: 'mediumint', name: 'mediumint' };
|
||||||
|
} else if (normalizedType === 'bigint') {
|
||||||
|
mappedType = { id: 'bigint', name: 'bigint' };
|
||||||
|
} else {
|
||||||
|
// Use the standard mapping for other types
|
||||||
|
mappedType = mapSQLTypeToGenericType(
|
||||||
|
column.type,
|
||||||
|
sourceDatabaseType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle PostgreSQL integer type specifically
|
||||||
|
else if (
|
||||||
|
sourceDatabaseType === DatabaseType.POSTGRESQL &&
|
||||||
|
(column.type.toLowerCase() === 'integer' ||
|
||||||
|
column.type.toLowerCase() === 'int' ||
|
||||||
|
column.type.toLowerCase() === 'int4')
|
||||||
|
) {
|
||||||
|
// Ensure integer types are preserved
|
||||||
|
mappedType = { id: 'integer', name: 'integer' };
|
||||||
|
} else {
|
||||||
|
// Use the standard mapping for other types
|
||||||
|
mappedType = mapSQLTypeToGenericType(
|
||||||
|
column.type,
|
||||||
|
sourceDatabaseType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const field: DBField = {
|
||||||
|
id: generateId(),
|
||||||
|
name: column.name,
|
||||||
|
type: mappedType,
|
||||||
|
nullable: column.nullable,
|
||||||
|
primaryKey: column.primaryKey,
|
||||||
|
unique: column.unique,
|
||||||
|
default: column.default || '',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
increment: column.increment,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add type arguments if present
|
||||||
|
if (column.typeArgs) {
|
||||||
|
// Transfer length for varchar/char types
|
||||||
|
if (
|
||||||
|
column.typeArgs.length !== undefined &&
|
||||||
|
(field.type.id === 'varchar' || field.type.id === 'char')
|
||||||
|
) {
|
||||||
|
field.characterMaximumLength =
|
||||||
|
column.typeArgs.length.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer precision/scale for numeric types
|
||||||
|
if (
|
||||||
|
column.typeArgs.precision !== undefined &&
|
||||||
|
(field.type.id === 'numeric' || field.type.id === 'decimal')
|
||||||
|
) {
|
||||||
|
field.precision = column.typeArgs.precision;
|
||||||
|
field.scale = column.typeArgs.scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return field;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create indexes
|
||||||
|
const indexes = table.indexes.map((sqlIndex) => {
|
||||||
|
const fieldIds = sqlIndex.columns.map((columnName) => {
|
||||||
|
const field = fields.find((f) => f.name === columnName);
|
||||||
|
if (!field) {
|
||||||
|
throw new Error(
|
||||||
|
`Index references non-existent column: ${columnName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return field.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: generateId(),
|
||||||
|
name: sqlIndex.name,
|
||||||
|
fieldIds,
|
||||||
|
unique: sqlIndex.unique,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: newId,
|
||||||
|
name: table.name,
|
||||||
|
schema: table.schema || '',
|
||||||
|
order: index,
|
||||||
|
fields,
|
||||||
|
indexes,
|
||||||
|
x: col * tableSpacing,
|
||||||
|
y: row * tableSpacing,
|
||||||
|
color: randomColor(),
|
||||||
|
isView: false,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process relationships
|
||||||
|
const relationships: DBRelationship[] = [];
|
||||||
|
|
||||||
|
parserResult.relationships.forEach((rel) => {
|
||||||
|
// First try to find the table with exact schema match
|
||||||
|
let sourceTable = tables.find(
|
||||||
|
(t) => t.name === rel.sourceTable && rel.sourceSchema === t.schema
|
||||||
|
);
|
||||||
|
|
||||||
|
// If not found, try without schema requirements
|
||||||
|
if (!sourceTable) {
|
||||||
|
sourceTable = tables.find((t) => t.name === rel.sourceTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar approach for target table
|
||||||
|
let targetTable = tables.find(
|
||||||
|
(t) => t.name === rel.targetTable && rel.targetSchema === t.schema
|
||||||
|
);
|
||||||
|
|
||||||
|
// If not found, try without schema requirements
|
||||||
|
if (!targetTable) {
|
||||||
|
targetTable = tables.find((t) => t.name === rel.targetTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceTable || !targetTable) {
|
||||||
|
console.warn('Relationship refers to non-existent table:', {
|
||||||
|
sourceTable: rel.sourceTable,
|
||||||
|
sourceSchema: rel.sourceSchema,
|
||||||
|
targetTable: rel.targetTable,
|
||||||
|
targetSchema: rel.targetSchema,
|
||||||
|
availableTables: tables.map(
|
||||||
|
(t) => `${t.schema || ''}.${t.name}`
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceTableId = tableIdMapping.get(rel.sourceTableId);
|
||||||
|
const targetTableId = tableIdMapping.get(rel.targetTableId);
|
||||||
|
|
||||||
|
if (!sourceTableId || !targetTableId) {
|
||||||
|
console.warn('Could not find mapped table IDs for relationship');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceField = sourceTable.fields.find(
|
||||||
|
(f) => f.name === rel.sourceColumn
|
||||||
|
);
|
||||||
|
const targetField = targetTable.fields.find(
|
||||||
|
(f) => f.name === rel.targetColumn
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!sourceField || !targetField) {
|
||||||
|
console.log('Relationship refers to non-existent field:', {
|
||||||
|
sourceTable: rel.sourceTable,
|
||||||
|
sourceField: rel.sourceColumn,
|
||||||
|
targetTable: rel.targetTable,
|
||||||
|
targetField: rel.targetColumn,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the cardinality from the SQL parser if available, otherwise determine it
|
||||||
|
const sourceCardinality =
|
||||||
|
rel.sourceCardinality ||
|
||||||
|
(sourceField.unique || sourceField.primaryKey ? 'one' : 'many');
|
||||||
|
const targetCardinality =
|
||||||
|
rel.targetCardinality ||
|
||||||
|
(targetField.unique || targetField.primaryKey ? 'one' : 'many');
|
||||||
|
|
||||||
|
relationships.push({
|
||||||
|
id: generateId(),
|
||||||
|
name: rel.name,
|
||||||
|
sourceSchema: sourceTable.schema,
|
||||||
|
targetSchema: targetTable.schema,
|
||||||
|
sourceTableId: sourceTableId,
|
||||||
|
targetTableId: targetTableId,
|
||||||
|
sourceFieldId: sourceField.id,
|
||||||
|
targetFieldId: targetField.id,
|
||||||
|
sourceCardinality,
|
||||||
|
targetCardinality,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const diagram = {
|
||||||
|
id: generateDiagramId(),
|
||||||
|
name: `SQL Import (${sourceDatabaseType})`,
|
||||||
|
databaseType: targetDatabaseType,
|
||||||
|
tables,
|
||||||
|
relationships,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return diagram;
|
||||||
|
}
|
||||||
239
src/lib/data/sql-import/dialect-importers/mysql/mysql-common.ts
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
export const parserOpts = {
|
||||||
|
database: 'MySQL', // Set dialect to MySQL
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define interfaces for AST nodes - Fixed no-explicit-any issues
|
||||||
|
export interface SQLAstNode {
|
||||||
|
type: string;
|
||||||
|
keyword?: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
[key: string]: any; // Need to keep any here for compatibility with Parser's AST
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a minimal interface for table objects used in helper functions
|
||||||
|
export interface TableLike {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
schema?: string;
|
||||||
|
columns: unknown[];
|
||||||
|
indexes: unknown[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableReference {
|
||||||
|
table?: string;
|
||||||
|
schema?: string;
|
||||||
|
db?: string; // Support for PostgreSQL AST compatibility
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColumnReference {
|
||||||
|
column?:
|
||||||
|
| string
|
||||||
|
| { value?: string; expr?: { value?: string; type?: string } };
|
||||||
|
expr?: { value?: string; type?: string };
|
||||||
|
value?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColumnDefinition {
|
||||||
|
resource: string;
|
||||||
|
column: string | ColumnReference;
|
||||||
|
definition?: {
|
||||||
|
dataType?: string;
|
||||||
|
constraint?: string;
|
||||||
|
length?: number;
|
||||||
|
precision?: number;
|
||||||
|
scale?: number;
|
||||||
|
};
|
||||||
|
primary_key?: string;
|
||||||
|
nullable?: { type?: string };
|
||||||
|
unique?: string;
|
||||||
|
default_val?: SQLAstNode;
|
||||||
|
auto_increment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConstraintDefinition {
|
||||||
|
resource: string;
|
||||||
|
constraint_type: string;
|
||||||
|
constraint_name?: string;
|
||||||
|
definition?: Array<ColumnReference> | { columns?: string[] };
|
||||||
|
columns?: string[];
|
||||||
|
reference_definition?: ReferenceDefinition;
|
||||||
|
reference?: ReferenceDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReferenceDefinition {
|
||||||
|
table?: string | TableReference | TableReference[];
|
||||||
|
columns?: Array<ColumnReference | string> | string[];
|
||||||
|
definition?: Array<ColumnReference>;
|
||||||
|
on_update?: string;
|
||||||
|
on_delete?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateTableStatement extends SQLAstNode {
|
||||||
|
table: TableReference | TableReference[];
|
||||||
|
create_definitions?: Array<ColumnDefinition | ConstraintDefinition>;
|
||||||
|
comment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateIndexStatement extends SQLAstNode {
|
||||||
|
table: TableReference | TableReference[] | string;
|
||||||
|
index?: string;
|
||||||
|
index_name?: string;
|
||||||
|
index_type?: string;
|
||||||
|
unique?: boolean;
|
||||||
|
columns?: Array<ColumnReference>;
|
||||||
|
index_columns?: Array<{ column?: ColumnReference } | ColumnReference>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlterTableConstraintDefinition extends ConstraintDefinition {
|
||||||
|
constraint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlterTableExprItem {
|
||||||
|
action: string;
|
||||||
|
resource?: string;
|
||||||
|
type?: string;
|
||||||
|
constraint?: { constraint_type?: string };
|
||||||
|
create_definitions?:
|
||||||
|
| AlterTableConstraintDefinition
|
||||||
|
| {
|
||||||
|
constraint_type?: string;
|
||||||
|
definition?: Array<ColumnReference>;
|
||||||
|
constraint?: string;
|
||||||
|
reference_definition?: ReferenceDefinition;
|
||||||
|
resource?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlterTableStatement extends SQLAstNode {
|
||||||
|
table: TableReference[] | TableReference | string;
|
||||||
|
expr: AlterTableExprItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define type for column type arguments
|
||||||
|
export interface TypeArgs {
|
||||||
|
length?: number;
|
||||||
|
precision?: number;
|
||||||
|
scale?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to extract column name from different AST formats
|
||||||
|
export function extractColumnName(
|
||||||
|
columnObj: string | ColumnReference | undefined
|
||||||
|
): string {
|
||||||
|
if (!columnObj) return '';
|
||||||
|
|
||||||
|
// Handle different formats based on actual AST structure
|
||||||
|
if (typeof columnObj === 'string') return columnObj;
|
||||||
|
|
||||||
|
if (typeof columnObj === 'object') {
|
||||||
|
// Direct column property
|
||||||
|
if (columnObj.column) {
|
||||||
|
if (typeof columnObj.column === 'string') return columnObj.column;
|
||||||
|
if (typeof columnObj.column === 'object') {
|
||||||
|
// Handle nested value property
|
||||||
|
if (columnObj.column.value) return columnObj.column.value;
|
||||||
|
// Handle expression property with value
|
||||||
|
if (columnObj.column.expr?.value)
|
||||||
|
return columnObj.column.expr.value;
|
||||||
|
// Handle double_quote_string type which is common in PostgreSQL
|
||||||
|
if (columnObj.column.expr?.type === 'double_quote_string')
|
||||||
|
return columnObj.column.expr.value || '';
|
||||||
|
// Direct access to expr
|
||||||
|
if (columnObj.column.expr?.type === 'default')
|
||||||
|
return columnObj.column.expr.value || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct expr property
|
||||||
|
if (columnObj.expr) {
|
||||||
|
if (columnObj.expr.type === 'default')
|
||||||
|
return columnObj.expr.value || '';
|
||||||
|
if (columnObj.expr.type === 'double_quote_string')
|
||||||
|
return columnObj.expr.value || '';
|
||||||
|
if (columnObj.expr.value) return columnObj.expr.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct value property
|
||||||
|
if (columnObj.value) return columnObj.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('Could not extract column name from:', columnObj);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to extract type arguments from column definition
|
||||||
|
export function getTypeArgs(
|
||||||
|
definition: ColumnDefinition['definition'] | undefined
|
||||||
|
): TypeArgs {
|
||||||
|
const typeArgs: TypeArgs = {};
|
||||||
|
|
||||||
|
if (!definition) return typeArgs;
|
||||||
|
|
||||||
|
if (definition.length !== undefined) {
|
||||||
|
typeArgs.length = definition.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (definition.scale !== undefined && definition.precision !== undefined) {
|
||||||
|
typeArgs.precision = definition.precision;
|
||||||
|
typeArgs.scale = definition.scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to find a table with consistent schema handling
|
||||||
|
export function findTableWithSchemaSupport(
|
||||||
|
tables: TableLike[],
|
||||||
|
tableName: string,
|
||||||
|
schemaName?: string
|
||||||
|
): TableLike | undefined {
|
||||||
|
// Default to public schema if none provided
|
||||||
|
const effectiveSchema = schemaName || 'public';
|
||||||
|
|
||||||
|
// First try with exact schema match
|
||||||
|
let table = tables.find(
|
||||||
|
(t) => t.name === tableName && t.schema === effectiveSchema
|
||||||
|
);
|
||||||
|
|
||||||
|
// If not found with schema, try with the legacy schema match
|
||||||
|
if (!table && schemaName) {
|
||||||
|
table = tables.find(
|
||||||
|
(t) => t.name === tableName && t.schema === schemaName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not found with schema, try any match on the table name
|
||||||
|
if (!table) {
|
||||||
|
table = tables.find((t) => t.name === tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to find table ID with schema support
|
||||||
|
export function getTableIdWithSchemaSupport(
|
||||||
|
tableMap: Record<string, string>,
|
||||||
|
tableName: string,
|
||||||
|
schemaName?: string
|
||||||
|
): string | undefined {
|
||||||
|
// Default to public schema if none provided
|
||||||
|
const effectiveSchema = schemaName || 'public';
|
||||||
|
|
||||||
|
// First try with schema
|
||||||
|
const tableKey = `${effectiveSchema}.${tableName}`;
|
||||||
|
let tableId = tableMap[tableKey];
|
||||||
|
|
||||||
|
// If not found with the effective schema, try with the original schema if different
|
||||||
|
if (!tableId && schemaName && schemaName !== effectiveSchema) {
|
||||||
|
const originalSchemaKey = `${schemaName}.${tableName}`;
|
||||||
|
tableId = tableMap[originalSchemaKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not found with schema, try without schema
|
||||||
|
if (!tableId) {
|
||||||
|
tableId = tableMap[tableName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableId;
|
||||||
|
}
|
||||||