Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
467ff697c9 | ||
|
|
d6919f3033 | ||
|
|
56382a9fdc | ||
|
|
e06eb2a48e | ||
|
|
543b716c77 | ||
|
|
b55d631146 | ||
|
|
ef118929ad | ||
|
|
68f48190c9 | ||
|
|
bba265ad43 | ||
|
|
cbc4e85a14 | ||
|
|
26a0a5b550 | ||
|
|
b935b7f251 | ||
|
|
a1c0cf102a | ||
|
|
ab89bad6d5 | ||
|
|
deb218423f | ||
|
|
48342471ac | ||
|
|
47bb87a88f | ||
|
|
a96c2e1078 | ||
|
|
26d95eed25 | ||
|
|
be65328f24 | ||
|
|
85fd14fa02 | ||
|
|
9c485b3b01 | ||
|
|
e993f1549c | ||
|
|
0db67ea42a | ||
|
|
b9e621bd68 | ||
|
|
93d59f8887 | ||
|
|
190e4f4ffa | ||
|
|
dc404c9d7e | ||
|
|
dd4324d64f | ||
|
|
1878083056 | ||
|
|
7b6271962a | ||
|
|
2edc8dfde8 | ||
|
|
004d530880 | ||
|
|
fd2cc9fcfc | ||
|
|
4c93326bb6 | ||
|
|
ef3d7a8b67 | ||
|
|
3b3be086b1 | ||
|
|
b424518212 | ||
|
|
99a8201398 | ||
|
|
eb9b41e4f6 | ||
|
|
fef6d3f499 | ||
|
|
14f11c27a7 | ||
|
|
2118bce0f0 | ||
|
|
88be6c1fd4 | ||
|
|
0dcc9b9568 | ||
|
|
ff3269ec05 | ||
|
|
659dc2e3e7 | ||
|
|
c36cd33180 | ||
|
|
58231c9139 | ||
|
|
1643e7bdeb | ||
|
|
42d4cbac8c | ||
|
|
7452ca6965 | ||
|
|
27aede7794 | ||
|
|
e9e2736cb2 | ||
|
|
74c1730425 | ||
|
|
94bed7fcce | ||
|
|
8abf2a7bfc | ||
|
|
ee659eaa03 | ||
|
|
7c5db0848e | ||
|
|
4b43f720e9 | ||
|
|
766b5164b8 | ||
|
|
7868ca9f42 | ||
|
|
0411742864 | ||
|
|
9831ac5a10 | ||
|
|
91c6fb9249 | ||
|
|
c155013668 | ||
|
|
1b0f293c87 | ||
|
|
df2dc03aa0 | ||
|
|
205d431c89 | ||
|
|
0abe18cdf9 | ||
|
|
a151f56b5d | ||
|
|
2b6b733261 | ||
|
|
b56b04925c | ||
|
|
635fb53c9f | ||
|
|
d6659795bc | ||
|
|
348f80568e | ||
|
|
5f9c74a9ad | ||
|
|
5409288388 | ||
|
|
2309306ef5 | ||
|
|
3574cecc7c | ||
|
|
29b8edc051 | ||
|
|
5fc10a7e64 | ||
|
|
807cd22e0c | ||
|
|
03772f6b4f | ||
|
|
885eb719de | ||
|
|
94656ec7a5 | ||
|
|
a0e966b64f | ||
|
|
a8fe491c1b | ||
|
|
ddeef3b134 | ||
|
|
d45677e92d | ||
|
|
9c7d03c285 | ||
|
|
be1b109f23 | ||
|
|
05eaf85a3d | ||
|
|
53f443d452 | ||
|
|
134c62f931 | ||
|
|
4bb4766e1a | ||
|
|
24db32369a | ||
|
|
e77ee60a5b | ||
|
|
6c65c2e9cc | ||
|
|
70f545f78b | ||
|
|
fb702c87ce | ||
|
|
eaa067814f | ||
|
|
667685ed0f | ||
|
|
2940431efa | ||
|
|
94ec43b608 | ||
|
|
a2efed803f | ||
|
|
8749591be0 | ||
|
|
c5e0ea6fa4 | ||
|
|
ab07da0b03 | ||
|
|
8397bef392 | ||
|
|
7c3c62860e | ||
|
|
76ba4ce4c5 | ||
|
|
0c0fad719f | ||
|
|
b75c6fe4e7 | ||
|
|
d9fcbeec72 | ||
|
|
5d79721b6d | ||
|
|
4be3592cf4 | ||
|
|
b4cdcbbbd7 | ||
|
|
e9c7f4be06 |
@@ -1,29 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: { browser: true, es2020: true },
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:react-hooks/recommended',
|
|
||||||
'plugin:css-modules/recommended',
|
|
||||||
'plugin:tailwindcss/recommended',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
// 'plugin:jsx-a11y/recommended',
|
|
||||||
],
|
|
||||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
plugins: ['react-refresh', 'css-modules', 'tailwindcss', 'jsx-a11y'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/consistent-type-imports': 'error',
|
|
||||||
'react-refresh/only-export-components': [
|
|
||||||
'warn',
|
|
||||||
{ allowConstantExport: true },
|
|
||||||
],
|
|
||||||
'react/no-unescaped-entities': 'off',
|
|
||||||
'react/prop-types': 'off',
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: { version: 'detect' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
170
CHANGELOG.md
@@ -1,5 +1,175 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.8.1](https://github.com/chartdb/chartdb/compare/v1.8.0...v1.8.1) (2025-03-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **add-docs:** add link to ChartDB documentation ([#597](https://github.com/chartdb/chartdb/issues/597)) ([b55d631](https://github.com/chartdb/chartdb/commit/b55d631146ff3a1f7d63c800d44b5d3d3a223c76))
|
||||||
|
* components config ([#591](https://github.com/chartdb/chartdb/issues/591)) ([cbc4e85](https://github.com/chartdb/chartdb/commit/cbc4e85a14e24a43f9ff470518f8fe2845046bdb))
|
||||||
|
* **docker config:** Environment Variable Handling and Configuration Logic ([#605](https://github.com/chartdb/chartdb/issues/605)) ([d6919f3](https://github.com/chartdb/chartdb/commit/d6919f30336cc846fe6e6505b5a5278aa14dcce6))
|
||||||
|
* **empty-state:** show diff buttons on import-dbml when triggered by empty ([#574](https://github.com/chartdb/chartdb/issues/574)) ([4834247](https://github.com/chartdb/chartdb/commit/48342471ac231922f2ca4455b74a9879127a54f1))
|
||||||
|
* **i18n:** add [FR] translation ([#579](https://github.com/chartdb/chartdb/issues/579)) ([ab89bad](https://github.com/chartdb/chartdb/commit/ab89bad6d544ba4c339a3360eeec7d29e5579511))
|
||||||
|
* **img-export:** add ChartDB watermark to exported image ([#588](https://github.com/chartdb/chartdb/issues/588)) ([b935b7f](https://github.com/chartdb/chartdb/commit/b935b7f25111d5f72b7f8d7c552a4ea5974f791e))
|
||||||
|
* **import-mssql:** fix import/export scripts to handle data correctly ([#598](https://github.com/chartdb/chartdb/issues/598)) ([e06eb2a](https://github.com/chartdb/chartdb/commit/e06eb2a48e6bd3bcf352f4bcf128214c7da4c1b1))
|
||||||
|
* **menu-backup:** update export to be backup ([#590](https://github.com/chartdb/chartdb/issues/590)) ([26a0a5b](https://github.com/chartdb/chartdb/commit/26a0a5b550ef5e47e89b00d0232dc98936f63f23))
|
||||||
|
* open create new diagram when there is no diagram ([#594](https://github.com/chartdb/chartdb/issues/594)) ([ef11892](https://github.com/chartdb/chartdb/commit/ef118929ad5d5cbfae0290061bd8ea30bd262496))
|
||||||
|
* **open diagram:** in case there is no diagram, opens the dialog ([#593](https://github.com/chartdb/chartdb/issues/593)) ([68f4819](https://github.com/chartdb/chartdb/commit/68f48190c93f155398cca15dd7af2a025de2d45f))
|
||||||
|
* **side-panel:** simplify how to add field and index ([#573](https://github.com/chartdb/chartdb/issues/573)) ([a1c0cf1](https://github.com/chartdb/chartdb/commit/a1c0cf102add4fb235e913e75078139b3961341b))
|
||||||
|
* **sql_server_export:** use sql server export ([#600](https://github.com/chartdb/chartdb/issues/600)) ([56382a9](https://github.com/chartdb/chartdb/commit/56382a9fdc5e3044f8811873dd8a79f590771896))
|
||||||
|
* **sqlite-import:** import nuallable columns correctly + add json type ([#571](https://github.com/chartdb/chartdb/issues/571)) ([deb2184](https://github.com/chartdb/chartdb/commit/deb218423f77f0c0945a93005696456f62b00ce3))
|
||||||
|
|
||||||
|
## [1.8.0](https://github.com/chartdb/chartdb/compare/v1.7.0...v1.8.0) (2025-02-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **dbml-import:** add error highlighting for dbml imports ([#556](https://github.com/chartdb/chartdb/issues/556)) ([190e4f4](https://github.com/chartdb/chartdb/commit/190e4f4ffa834fa621f264dc608ca3f3b393a331))
|
||||||
|
* **docker image:** add support for custom inference servers ([#543](https://github.com/chartdb/chartdb/issues/543)) ([1878083](https://github.com/chartdb/chartdb/commit/1878083056ea4db7a05cdeeb38a4f7b9f5f95bd1))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **canvas:** add right-click option to create relationships ([#568](https://github.com/chartdb/chartdb/issues/568)) ([e993f15](https://github.com/chartdb/chartdb/commit/e993f1549c4c86bb9e7e36062db803ba6613b3b3))
|
||||||
|
* **canvas:** locate table from canvas ([#560](https://github.com/chartdb/chartdb/issues/560)) ([dc404c9](https://github.com/chartdb/chartdb/commit/dc404c9d7ee272c93aac69646bac859829a5234e))
|
||||||
|
* **docker:** add option to hide popups ([#580](https://github.com/chartdb/chartdb/issues/580)) ([a96c2e1](https://github.com/chartdb/chartdb/commit/a96c2e107838d2dc13b586923fd9dbe06598cdd8))
|
||||||
|
* **export-sql:** show create script for only filtered schemas ([#570](https://github.com/chartdb/chartdb/issues/570)) ([85fd14f](https://github.com/chartdb/chartdb/commit/85fd14fa02bb2879c36bba53369dbf2e7fa578d4))
|
||||||
|
* **i18n:** fix Ukrainian ([#554](https://github.com/chartdb/chartdb/issues/554)) ([7b62719](https://github.com/chartdb/chartdb/commit/7b6271962a99bfe5ffbd0176e714c76368ef5c41))
|
||||||
|
* **import dbml:** add import for indexes ([#566](https://github.com/chartdb/chartdb/issues/566)) ([0db67ea](https://github.com/chartdb/chartdb/commit/0db67ea42a5f9585ca1d246db7a7ff0239bec0ba))
|
||||||
|
* **import-query:** improve the cleanup for messy json input ([#562](https://github.com/chartdb/chartdb/issues/562)) ([93d59f8](https://github.com/chartdb/chartdb/commit/93d59f8887765098d040a3184aaee32112f67267))
|
||||||
|
* **index unique:** extract unique toggle for faster editing ([#559](https://github.com/chartdb/chartdb/issues/559)) ([dd4324d](https://github.com/chartdb/chartdb/commit/dd4324d64f7638ada5c022a2ab38bd8e6986af25))
|
||||||
|
* **mssql-import:** improve script readability by adding edition comment ([#572](https://github.com/chartdb/chartdb/issues/572)) ([be65328](https://github.com/chartdb/chartdb/commit/be65328f24b0361638b9e2edb39eaa9906e77f67))
|
||||||
|
* **realtionships section:** add the schema to source/target tables ([#561](https://github.com/chartdb/chartdb/issues/561)) ([b9e621b](https://github.com/chartdb/chartdb/commit/b9e621bd680730a0ffbf1054d735bfa418711cae))
|
||||||
|
* **sqlserver-import:** open ssms guide when max chars ([#565](https://github.com/chartdb/chartdb/issues/565)) ([9c485b3](https://github.com/chartdb/chartdb/commit/9c485b3b01a131bf551c7e95916b0c416f6aa0b5))
|
||||||
|
* **table actions:** fix size of table actions ([#578](https://github.com/chartdb/chartdb/issues/578)) ([26d95ee](https://github.com/chartdb/chartdb/commit/26d95eed25d86452d9168a9d93a301ba50d934e3))
|
||||||
|
|
||||||
|
## [1.7.0](https://github.com/chartdb/chartdb/compare/v1.6.1...v1.7.0) (2025-02-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **dbml-editor:** add dbml editor in side pannel ([#534](https://github.com/chartdb/chartdb/issues/534)) ([88be6c1](https://github.com/chartdb/chartdb/commit/88be6c1fd4a7e1f20937e8204c14d8fc1c2665b4))
|
||||||
|
* **import-dbml:** add import dbml functionality ([#549](https://github.com/chartdb/chartdb/issues/549)) ([b424518](https://github.com/chartdb/chartdb/commit/b424518212290a870fdb7c420a303f65f5901429))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **canvas edit:** add option to edit names in canvas ([#536](https://github.com/chartdb/chartdb/issues/536)) ([0dcc9b9](https://github.com/chartdb/chartdb/commit/0dcc9b9568cfe749d44d2e93cb365ba3d3a1e71c))
|
||||||
|
* **dbml-editor:** add shortcuts to dbml and filter: [#534](https://github.com/chartdb/chartdb/issues/534) ([#535](https://github.com/chartdb/chartdb/issues/535)) ([3b3be08](https://github.com/chartdb/chartdb/commit/3b3be086b1e8d5acf999f8504580d9e2f956f7da))
|
||||||
|
* **dbml:** add error handling ([#545](https://github.com/chartdb/chartdb/issues/545)) ([fef6d3f](https://github.com/chartdb/chartdb/commit/fef6d3f4996130a3769d1f25b4b1f2090293a1bf))
|
||||||
|
* **empty-state:** fix dark-mode for empty-state ([#547](https://github.com/chartdb/chartdb/issues/547)) ([99a8201](https://github.com/chartdb/chartdb/commit/99a820139861546a012d7b562ddbb9b77698151a))
|
||||||
|
* **examples:** fix employee example dbml ([#544](https://github.com/chartdb/chartdb/issues/544)) ([2118bce](https://github.com/chartdb/chartdb/commit/2118bce0f00d55eb19d22b9fa2d4964ba2533a09))
|
||||||
|
* **i18n:** translation/Ukrainian ([#529](https://github.com/chartdb/chartdb/issues/529)) ([ff3269e](https://github.com/chartdb/chartdb/commit/ff3269ec0510bbae4bc114e65a1ea86a656e8785))
|
||||||
|
* **open-diagram:** add arrow keys navigation in open diagram dialog ([#537](https://github.com/chartdb/chartdb/issues/537)) ([14f11c2](https://github.com/chartdb/chartdb/commit/14f11c27a7ad5b990131c8495148cabf12835082))
|
||||||
|
* **performance:** fix bundle size ([#551](https://github.com/chartdb/chartdb/issues/551)) ([4c93326](https://github.com/chartdb/chartdb/commit/4c93326bb6e3eaa143373c500a0c641e95a53fb9))
|
||||||
|
* **performance:** reduce bundle size ([#553](https://github.com/chartdb/chartdb/issues/553)) ([004d530](https://github.com/chartdb/chartdb/commit/004d530880a50dea6e9786eb9ae63cf592a4d852))
|
||||||
|
* **performance:** resolve error on startup ([#552](https://github.com/chartdb/chartdb/issues/552)) ([fd2cc9f](https://github.com/chartdb/chartdb/commit/fd2cc9fcfc8f4a9f0bc79def47d89114159392fb))
|
||||||
|
* **psql-import:** remove typo for import command (psql) ([#546](https://github.com/chartdb/chartdb/issues/546)) ([eb9b41e](https://github.com/chartdb/chartdb/commit/eb9b41e4f656bec1451c45763f4ea5b547aeec5c))
|
||||||
|
* **scroll:** fix scroll area ([#550](https://github.com/chartdb/chartdb/issues/550)) ([ef3d7a8](https://github.com/chartdb/chartdb/commit/ef3d7a8b67431e923b75bf8287b86bbc8abe723b))
|
||||||
|
|
||||||
|
## [1.6.1](https://github.com/chartdb/chartdb/compare/v1.6.0...v1.6.1) (2025-01-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* change empty state image ([#531](https://github.com/chartdb/chartdb/issues/531)) ([42d4cba](https://github.com/chartdb/chartdb/commit/42d4cbac8ce352e0e4e155d7003bfb85296b897f))
|
||||||
|
* **chat-type:** remove typo of char datatype in examples ([#530](https://github.com/chartdb/chartdb/issues/530)) ([58231c9](https://github.com/chartdb/chartdb/commit/58231c91393de30ebff817f0ebc57a5c5579f106))
|
||||||
|
* **empty_state:** customize empty state ([#533](https://github.com/chartdb/chartdb/issues/533)) ([1643e7b](https://github.com/chartdb/chartdb/commit/1643e7bdeb1bbaf081ab064e871d102c87243c0a))
|
||||||
|
* **Image Export:** importing css rules error while download image ([#524](https://github.com/chartdb/chartdb/issues/524)) ([e9e2736](https://github.com/chartdb/chartdb/commit/e9e2736cb2203702d53df9afc30b8e989a8c9953))
|
||||||
|
* **shortcuts:** add zoom all shortcut ([#528](https://github.com/chartdb/chartdb/issues/528)) ([7452ca6](https://github.com/chartdb/chartdb/commit/7452ca6965b0332a93b686c397ddf51013e42506))
|
||||||
|
* **filter-tables:** show clean filter if no-results ([#532](https://github.com/chartdb/chartdb/issues/532)) ([c36cd33](https://github.com/chartdb/chartdb/commit/c36cd33180badaa9b7f9e27c765f19cb03a50ccd))
|
||||||
|
|
||||||
|
## [1.6.0](https://github.com/chartdb/chartdb/compare/v1.5.1...v1.6.0) (2025-01-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **view-menu:** add toggle for mini map visibility ([#496](https://github.com/chartdb/chartdb/issues/496)) ([#505](https://github.com/chartdb/chartdb/issues/505)) ([8abf2a7](https://github.com/chartdb/chartdb/commit/8abf2a7bfcc36d39e60ac133b0e5e569de1bbc72))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add loadDiagramFromData logic to chartdb provider ([#513](https://github.com/chartdb/chartdb/issues/513)) ([ee659ea](https://github.com/chartdb/chartdb/commit/ee659eaa038a94ee13801801e84152df4d79683d))
|
||||||
|
* **dependency:** upgrade react query to v7 - clean console warnings ([#504](https://github.com/chartdb/chartdb/issues/504)) ([7c5db08](https://github.com/chartdb/chartdb/commit/7c5db0848e49dfdb7e7120f77003d1e37f8d71b0))
|
||||||
|
* **i18n:** translation/Arabic ([#509](https://github.com/chartdb/chartdb/issues/509)) ([4b43f72](https://github.com/chartdb/chartdb/commit/4b43f720e90e49d5461e68d188e3865000f52497))
|
||||||
|
|
||||||
|
## [1.5.1](https://github.com/chartdb/chartdb/compare/v1.5.0...v1.5.1) (2024-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **export:** fix SQL server field.nullable type to boolean ([#486](https://github.com/chartdb/chartdb/issues/486)) ([a151f56](https://github.com/chartdb/chartdb/commit/a151f56b5d950e0b5cc54363684ada95889024b3))
|
||||||
|
* **readme:** Update README.md - add CockroachDB ([#482](https://github.com/chartdb/chartdb/issues/482)) ([2b6b733](https://github.com/chartdb/chartdb/commit/2b6b73326155f18d6d56779c0657a3506e2d2cde))
|
||||||
|
|
||||||
|
## [1.5.0](https://github.com/chartdb/chartdb/compare/v1.4.0...v1.5.0) (2024-12-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **CockroachDB:** Add CockroachDB support ([#472](https://github.com/chartdb/chartdb/issues/472)) ([5409288](https://github.com/chartdb/chartdb/commit/54092883883b135f6ace51d86754b1df76603d30))
|
||||||
|
* **i18n:** translate share and dialog sections in Indonesian locale files ([#468](https://github.com/chartdb/chartdb/issues/468)) ([3574cec](https://github.com/chartdb/chartdb/commit/3574cecc7c73dcab404b82115d20e1ad0cd26b37))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **core:** fix update diagram id ([#477](https://github.com/chartdb/chartdb/issues/477)) ([348f805](https://github.com/chartdb/chartdb/commit/348f80568e0f686ee478147fdc43a5d43b5c1ebb))
|
||||||
|
* **dialogs:** fix footer position on dialogs ([#470](https://github.com/chartdb/chartdb/issues/470)) ([2309306](https://github.com/chartdb/chartdb/commit/2309306ef590783b00a2489209092107dd9a3788))
|
||||||
|
* **sql-server import:** nullable should be boolean instead of string ([#480](https://github.com/chartdb/chartdb/issues/480)) ([635fb53](https://github.com/chartdb/chartdb/commit/635fb53c9f7ebd1e5ef4d9274af041edc08f04c3))
|
||||||
|
|
||||||
|
## [1.4.0](https://github.com/chartdb/chartdb/compare/v1.3.1...v1.4.0) (2024-12-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **add templates:** add six more templates ([#452](https://github.com/chartdb/chartdb/issues/452)) ([be1b109](https://github.com/chartdb/chartdb/commit/be1b109f23e62df4cc63fa8914c2754f7809cc08))
|
||||||
|
* **add templates:** add six more templates (django-axes, laravel-activitylog, octobox, pay-rails, pixelfed, polr) ([#460](https://github.com/chartdb/chartdb/issues/460)) ([03772f6](https://github.com/chartdb/chartdb/commit/03772f6b4f99f9c4350356aa0f2a4666f4f1794d))
|
||||||
|
* **add templates:** add six more templates (reversion, screeenly, staytus, deployer, devise, talk) ([#457](https://github.com/chartdb/chartdb/issues/457)) ([ddeef3b](https://github.com/chartdb/chartdb/commit/ddeef3b134efa893e1c1e15e2f87c27157200e2d))
|
||||||
|
* **clickhouse:** add ClickHouse support ([#463](https://github.com/chartdb/chartdb/issues/463)) ([807cd22](https://github.com/chartdb/chartdb/commit/807cd22e0c739f339fa07fe1d2f043c5411ae41f))
|
||||||
|
* **i18n:** Added bangla translations ([#432](https://github.com/chartdb/chartdb/issues/432)) ([885eb71](https://github.com/chartdb/chartdb/commit/885eb719de577c2652fbed1ed287f38fcc98c148))
|
||||||
|
* **side-panel:** Add functionality of order tables by drag & drop ([#425](https://github.com/chartdb/chartdb/issues/425)) ([a0e966b](https://github.com/chartdb/chartdb/commit/a0e966b64f8070d4595d47b2fb39e8bbf427b794))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **clipboard:** defensive for navigator clipboard ([#462](https://github.com/chartdb/chartdb/issues/462)) ([5fc10a7](https://github.com/chartdb/chartdb/commit/5fc10a7e649fc5877bb297b519b1b6a8b81f1323))
|
||||||
|
* **import-database:** update database type after importing into an existing generic diagra ([#456](https://github.com/chartdb/chartdb/issues/456)) ([a8fe491](https://github.com/chartdb/chartdb/commit/a8fe491c1b5a30d9f4144cefa9111dd3dfd5df1a))
|
||||||
|
* **Last Saved:** Translate the "last saved" relative date message ([#400](https://github.com/chartdb/chartdb/issues/400)) ([d45677e](https://github.com/chartdb/chartdb/commit/d45677e92d72efc6cea8f865ce46f0be6ec9961f))
|
||||||
|
* **mariadb-types:** Add uuid data type ([#459](https://github.com/chartdb/chartdb/issues/459)) ([94656ec](https://github.com/chartdb/chartdb/commit/94656ec7a5435c2da262fb3bc6a6d381d554b0c1))
|
||||||
|
* window type ([#454](https://github.com/chartdb/chartdb/issues/454)) ([9c7d03c](https://github.com/chartdb/chartdb/commit/9c7d03c285ff6f818eef3199c9b7a530d03a1fec))
|
||||||
|
|
||||||
|
## [1.3.1](https://github.com/chartdb/chartdb/compare/v1.3.0...v1.3.1) (2024-11-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **docker:** make OPENAI_API_KEY optional in docker run ([#448](https://github.com/chartdb/chartdb/issues/448)) ([4bb4766](https://github.com/chartdb/chartdb/commit/4bb4766e1ac8d69e138668eb8a46de5affe62ceb))
|
||||||
|
|
||||||
|
## [1.3.0](https://github.com/chartdb/chartdb/compare/v1.2.0...v1.3.0) (2024-11-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **side panel:** collapsible side panel on desktop view + keyboard shortcut ([#439](https://github.com/chartdb/chartdb/issues/439)) ([70f545f](https://github.com/chartdb/chartdb/commit/70f545f78bab9c510a6e5936fa5b259b806b6c69))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **dialogs:** fix height of dialogs for small screens ([#440](https://github.com/chartdb/chartdb/issues/440)) ([667685e](https://github.com/chartdb/chartdb/commit/667685ed0f6a8cc61ae86b3ba60e052fbe6a9e1a))
|
||||||
|
* **drawer:** set fix min size ([#429](https://github.com/chartdb/chartdb/issues/429)) ([c5e0ea6](https://github.com/chartdb/chartdb/commit/c5e0ea6fa4017666ff3bc1e3071c487df48afd3d))
|
||||||
|
* **export-sql:** add unique to export script ([#422](https://github.com/chartdb/chartdb/issues/422)) ([b75c6fe](https://github.com/chartdb/chartdb/commit/b75c6fe4e78f3e2058be680f2fa0442db3b4a6bd))
|
||||||
|
* fix layout warnings ([#434](https://github.com/chartdb/chartdb/issues/434)) ([94ec43b](https://github.com/chartdb/chartdb/commit/94ec43b60845bb8c3592ce1b1450ca0171a53f99))
|
||||||
|
* **i18n:** add bahasa indonesia translation ([#331](https://github.com/chartdb/chartdb/issues/331)) ([ab07da0](https://github.com/chartdb/chartdb/commit/ab07da0b031f0d4050ff6b44ddcb94cb6c0010b6))
|
||||||
|
* **i18n:** add missing type to vi.ts ([#444](https://github.com/chartdb/chartdb/issues/444)) ([e77ee60](https://github.com/chartdb/chartdb/commit/e77ee60a5b47e0854d11b0ee2f16d6956737d0ff))
|
||||||
|
* **i18n:** Add Telugu Language ([#352](https://github.com/chartdb/chartdb/issues/352)) ([8749591](https://github.com/chartdb/chartdb/commit/8749591be036e131de4bfeed1e6eece8d62980dd))
|
||||||
|
* **i18n:** add Turkish translations ([#315](https://github.com/chartdb/chartdb/issues/315)) ([d9fcbee](https://github.com/chartdb/chartdb/commit/d9fcbeec726b7bde9f7d202bf09dc6b617e3ad80))
|
||||||
|
* **i18n:** add Vietnamese translations ([#435](https://github.com/chartdb/chartdb/issues/435)) ([6c65c2e](https://github.com/chartdb/chartdb/commit/6c65c2e9cce600b9778b84ce5b5f1625dc6f1a58))
|
||||||
|
* **i18n:** Translating to Gujarati language ([#433](https://github.com/chartdb/chartdb/issues/433)) ([2940431](https://github.com/chartdb/chartdb/commit/2940431efa1a6aa54d80c61d5e05f0ad47cd67ba))
|
||||||
|
* **i18n:** Translation of the export error into Russian ([#418](https://github.com/chartdb/chartdb/issues/418)) ([7c3c628](https://github.com/chartdb/chartdb/commit/7c3c62860efc98d3aabf2132a79ac945ffc8315a))
|
||||||
|
* **i18n:** update korean for 1.2.0 ([#419](https://github.com/chartdb/chartdb/issues/419)) ([8397bef](https://github.com/chartdb/chartdb/commit/8397bef3924610d94661aae99c55ba4fa376a186))
|
||||||
|
* **import script:** remove double quotes ([#442](https://github.com/chartdb/chartdb/issues/442)) ([fb702c8](https://github.com/chartdb/chartdb/commit/fb702c87ce5254bf6e0209c692305f5086956090))
|
||||||
|
* **share:** fix export to handle broken indexes & relationships ([#416](https://github.com/chartdb/chartdb/issues/416)) ([4be3592](https://github.com/chartdb/chartdb/commit/4be3592cf4d160be83ddf1db01ffe9afdef119fa))
|
||||||
|
* **templates:** add Five more templates (bouncer, cabot, feedbin, Pythonic, flarum, freescout) ([#441](https://github.com/chartdb/chartdb/issues/441)) ([eaa0678](https://github.com/chartdb/chartdb/commit/eaa067814fd96fcc1ee10488ee747a71a8e8ec7a))
|
||||||
|
|
||||||
## [1.2.0](https://github.com/chartdb/chartdb/compare/v1.1.0...v1.2.0) (2024-11-17)
|
## [1.2.0](https://github.com/chartdb/chartdb/compare/v1.1.0...v1.2.0) (2024-11-17)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
Dockerfile
@@ -1,6 +1,9 @@
|
|||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
ARG VITE_OPENAI_API_KEY
|
ARG VITE_OPENAI_API_KEY
|
||||||
|
ARG VITE_OPENAI_API_ENDPOINT
|
||||||
|
ARG VITE_LLM_MODEL_NAME
|
||||||
|
ARG VITE_HIDE_BUCKLE_DOT_DEV
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
@@ -10,15 +13,20 @@ RUN npm ci
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
RUN echo "VITE_OPENAI_API_KEY=${VITE_OPENAI_API_KEY}" > .env && \
|
||||||
|
echo "VITE_OPENAI_API_ENDPOINT=${VITE_OPENAI_API_ENDPOINT}" >> .env && \
|
||||||
|
echo "VITE_LLM_MODEL_NAME=${VITE_LLM_MODEL_NAME}" >> .env && \
|
||||||
|
echo "VITE_HIDE_BUCKLE_DOT_DEV=${VITE_HIDE_BUCKLE_DOT_DEV}" >> .env
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Use a lightweight web server to serve the production build
|
|
||||||
FROM nginx:stable-alpine AS production
|
FROM nginx:stable-alpine AS production
|
||||||
|
|
||||||
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
|
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
|
||||||
COPY ./default.conf /etc/nginx/conf.d/default.conf
|
COPY ./default.conf.template /etc/nginx/conf.d/default.conf.template
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
# Expose the default port for the Nginx web server
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
39
README.md
@@ -16,6 +16,7 @@
|
|||||||
<h3 align="center">
|
<h3 align="center">
|
||||||
<a href="https://discord.gg/QeFwyWSKwC">Community</a> •
|
<a href="https://discord.gg/QeFwyWSKwC">Community</a> •
|
||||||
<a href="https://www.chartdb.io?ref=github_readme">Website</a> •
|
<a href="https://www.chartdb.io?ref=github_readme">Website</a> •
|
||||||
|
<a href="https://chartdb.io/templates?ref=github_readme">Examples</a> •
|
||||||
<a href="https://app.chartdb.io?ref=github_readme">Demo</a>
|
<a href="https://app.chartdb.io?ref=github_readme">Demo</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ ChartDB is currently in Public Beta. Star and watch this repository to get notif
|
|||||||
- ✅ SQL Server
|
- ✅ SQL Server
|
||||||
- ✅ MariaDB
|
- ✅ MariaDB
|
||||||
- ✅ SQLite
|
- ✅ SQLite
|
||||||
|
- ✅ CockroachDB
|
||||||
- ✅ ClickHouse
|
- ✅ ClickHouse
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
@@ -94,15 +96,44 @@ npm install
|
|||||||
VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
|
VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running the Docker Container
|
### Run the Docker Container
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t chartdb . (If you want AI capabilities, use `docker build --build-arg VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -t chartdb .`)
|
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 ghcr.io/chartdb/chartdb:latest
|
||||||
docker run -p 8080:80 chartdb
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Build and Run locally
|
||||||
|
```bash
|
||||||
|
docker build -t chartdb .
|
||||||
|
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 chartdb
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Custom Inference Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
docker build \
|
||||||
|
--build-arg VITE_OPENAI_API_ENDPOINT=<YOUR_ENDPOINT> \
|
||||||
|
--build-arg VITE_LLM_MODEL_NAME=<YOUR_MODEL_NAME> \
|
||||||
|
-t chartdb .
|
||||||
|
|
||||||
|
# Run
|
||||||
|
docker run \
|
||||||
|
-e OPENAI_API_ENDPOINT=<YOUR_ENDPOINT> \
|
||||||
|
-e LLM_MODEL_NAME=<YOUR_MODEL_NAME> \
|
||||||
|
-p 8080:80 chartdb
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** You must configure either Option 1 (OpenAI API key) OR Option 2 (Custom endpoint and model name) for AI capabilities to work. Do not mix the two options.
|
||||||
|
|
||||||
Open your browser and navigate to `http://localhost:8080`.
|
Open your browser and navigate to `http://localhost:8080`.
|
||||||
|
|
||||||
|
Example configuration for a local vLLM server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VITE_OPENAI_API_ENDPOINT=http://localhost:8000/v1
|
||||||
|
VITE_LLM_MODEL_NAME=Qwen/Qwen2.5-32B-Instruct-AWQ
|
||||||
|
```
|
||||||
|
|
||||||
## Try it on our website
|
## Try it on our website
|
||||||
|
|
||||||
1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)
|
1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
"style": "new-york",
|
"style": "new-york",
|
||||||
"rsc": false,
|
"rsc": false,
|
||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "tailwind.config.js",
|
"config": "tailwind.config.js",
|
||||||
"css": "src/globals.css",
|
"css": "src/globals.css",
|
||||||
"baseColor": "slate",
|
"baseColor": "slate",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "src/components",
|
"components": "src/components",
|
||||||
"utils": "@/lib/utils"
|
"utils": "src/lib/utils",
|
||||||
}
|
"ui": "src/components/ui",
|
||||||
|
"lib": "src/lib",
|
||||||
|
"hooks": "src/hooks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
15
default.conf
@@ -1,15 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html index.htm;
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
default.conf.template
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /config.js {
|
||||||
|
default_type application/javascript;
|
||||||
|
return 200 "window.env = {
|
||||||
|
OPENAI_API_KEY: \"$OPENAI_API_KEY\",
|
||||||
|
OPENAI_API_ENDPOINT: \"$OPENAI_API_ENDPOINT\",
|
||||||
|
LLM_MODEL_NAME: \"$LLM_MODEL_NAME\",
|
||||||
|
HIDE_BUCKLE_DOT_DEV: \"$HIDE_BUCKLE_DOT_DEV\"
|
||||||
|
};";
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
entrypoint.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Replace placeholders in nginx.conf
|
||||||
|
envsubst '${OPENAI_API_KEY} ${OPENAI_API_ENDPOINT} ${LLM_MODEL_NAME} ${HIDE_BUCKLE_DOT_DEV}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Start Nginx
|
||||||
|
nginx -g "daemon off;"
|
||||||
77
eslint.config.mjs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||||
|
import cssModules from 'eslint-plugin-css-modules';
|
||||||
|
import tailwindcss from 'eslint-plugin-tailwindcss';
|
||||||
|
import jsxA11Y from 'eslint-plugin-jsx-a11y';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: ['**/dist', '**/.eslintrc.cjs', '**/tailwind.config.js'],
|
||||||
|
// files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
},
|
||||||
|
...fixupConfigRules(
|
||||||
|
compat.extends(
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:css-modules/recommended',
|
||||||
|
'plugin:tailwindcss/recommended',
|
||||||
|
'plugin:prettier/recommended'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
'css-modules': fixupPluginRules(cssModules),
|
||||||
|
tailwindcss: fixupPluginRules(tailwindcss),
|
||||||
|
'jsx-a11y': jsxA11Y,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
// parserOptions: {
|
||||||
|
// project: './tsconfig.json',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
allowConstantExport: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'react/no-unescaped-entities': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'@typescript-eslint/no-empty-object-type': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
<script src="/config.js"></script>
|
||||||
<script
|
<script
|
||||||
src="https://cdn.usefathom.com/script.js"
|
src="https://cdn.usefathom.com/script.js"
|
||||||
data-site="PRHIVBNN"
|
data-site="PRHIVBNN"
|
||||||
|
|||||||
20759
package-lock.json
generated
195
package.json
@@ -1,97 +1,102 @@
|
|||||||
{
|
{
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.0",
|
"version": "1.8.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "npm run lint && tsc -b && vite build",
|
"build": "npm run lint && tsc -b && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.51",
|
"@ai-sdk/openai": "^0.0.51",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dbml/core": "^3.9.5",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
"@radix-ui/react-avatar": "^1.1.0",
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-checkbox": "^1.1.1",
|
"@radix-ui/react-avatar": "^1.1.0",
|
||||||
"@radix-ui/react-collapsible": "^1.1.0",
|
"@radix-ui/react-checkbox": "^1.1.1",
|
||||||
"@radix-ui/react-context-menu": "^2.2.1",
|
"@radix-ui/react-collapsible": "^1.1.0",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-context-menu": "^2.2.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-hover-card": "^1.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-hover-card": "^1.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-menubar": "^1.1.1",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-menubar": "^1.1.1",
|
||||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
"@radix-ui/react-select": "^2.1.1",
|
"@radix-ui/react-scroll-area": "1.2.0",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-select": "^2.1.1",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"@uidotdev/usehooks": "^2.4.1",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"@xyflow/react": "^12.3.1",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"ahooks": "^3.8.1",
|
"@xyflow/react": "^12.3.1",
|
||||||
"ai": "^3.3.14",
|
"ahooks": "^3.8.1",
|
||||||
"class-variance-authority": "^0.7.0",
|
"ai": "^3.3.14",
|
||||||
"clsx": "^2.1.1",
|
"class-variance-authority": "^0.7.0",
|
||||||
"cmdk": "^1.0.0",
|
"clsx": "^2.1.1",
|
||||||
"dexie": "^4.0.8",
|
"cmdk": "^1.0.0",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"dexie": "^4.0.8",
|
||||||
"html-to-image": "^1.11.11",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"i18next": "^23.14.0",
|
"html-to-image": "^1.11.11",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next": "^23.14.0",
|
||||||
"lucide-react": "^0.441.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"monaco-editor": "^0.52.0",
|
"lucide-react": "^0.441.0",
|
||||||
"nanoid": "^5.0.7",
|
"monaco-editor": "^0.52.0",
|
||||||
"node-sql-parser": "^5.3.2",
|
"nanoid": "^5.0.7",
|
||||||
"react": "^18.3.1",
|
"node-sql-parser": "^5.3.2",
|
||||||
"react-dom": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-helmet-async": "^2.0.5",
|
"react-dom": "^18.3.1",
|
||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-helmet-async": "^2.0.5",
|
||||||
"react-i18next": "^15.0.1",
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"react-resizable-panels": "^2.0.22",
|
"react-i18next": "^15.0.1",
|
||||||
"react-responsive": "^10.0.0",
|
"react-resizable-panels": "^2.0.22",
|
||||||
"react-router-dom": "^6.26.0",
|
"react-responsive": "^10.0.0",
|
||||||
"react-use": "^17.5.1",
|
"react-router-dom": "^7.1.1",
|
||||||
"tailwind-merge": "^2.4.0",
|
"react-use": "^17.5.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwind-merge": "^2.4.0",
|
||||||
"timeago-react": "^3.0.6",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^0.9.1",
|
"timeago-react": "^3.0.6",
|
||||||
"zod": "^3.23.8"
|
"vaul": "^0.9.1",
|
||||||
},
|
"zod": "^3.23.8"
|
||||||
"devDependencies": {
|
},
|
||||||
"@types/node": "^22.1.0",
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.3",
|
"@eslint/compat": "^1.2.4",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
"@eslint/js": "^9.16.0",
|
||||||
"@typescript-eslint/parser": "^7.15.0",
|
"@types/node": "^22.1.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@types/react": "^18.3.3",
|
||||||
"autoprefixer": "^10.4.20",
|
"@types/react-dom": "^18.3.0",
|
||||||
"eslint": "^8.57.0",
|
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"@typescript-eslint/parser": "^8.18.0",
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint": "^9.16.0",
|
||||||
"eslint-plugin-react": "^7.35.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.7",
|
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||||
"eslint-plugin-tailwindcss": "^3.17.4",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"husky": "^9.1.5",
|
"eslint-plugin-react": "^7.35.0",
|
||||||
"postcss": "^8.4.40",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"prettier": "^3.3.3",
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"eslint-plugin-tailwindcss": "^3.17.4",
|
||||||
"tailwindcss": "^3.4.7",
|
"globals": "^15.13.0",
|
||||||
"typescript": "^5.2.2",
|
"husky": "^9.1.5",
|
||||||
"unplugin-inject-preload": "^3.0.0",
|
"postcss": "^8.4.40",
|
||||||
"vite": "^5.3.4"
|
"prettier": "^3.3.3",
|
||||||
}
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
|
"tailwindcss": "^3.4.7",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"unplugin-inject-preload": "^3.0.0",
|
||||||
|
"vite": "^5.3.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/buckle-animated.gif
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
public/buckle.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
0
public/config.js
Normal file
BIN
src/assets/clickhouse_logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/clickhouse_logo_2.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/clickhouse_logo_dark.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
src/assets/cockroachdb_logo.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/cockroachdb_logo_2.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
src/assets/cockroachdb_logo_dark.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 6.1 KiB |
BIN
src/assets/empty_state_dark.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/assets/templates/bouncer-db-dark.png
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
src/assets/templates/bouncer-db.png
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
src/assets/templates/cabot-db-dark.png
Normal file
|
After Width: | Height: | Size: 420 KiB |
BIN
src/assets/templates/cabot-db.png
Normal file
|
After Width: | Height: | Size: 498 KiB |
BIN
src/assets/templates/cachet-db-dark.png
Normal file
|
After Width: | Height: | Size: 447 KiB |
BIN
src/assets/templates/cachet-db.png
Normal file
|
After Width: | Height: | Size: 486 KiB |
BIN
src/assets/templates/canvas-db-dark.png
Normal file
|
After Width: | Height: | Size: 346 KiB |
BIN
src/assets/templates/canvas-db.png
Normal file
|
After Width: | Height: | Size: 379 KiB |
BIN
src/assets/templates/deployer-db-dark.png
Normal file
|
After Width: | Height: | Size: 424 KiB |
BIN
src/assets/templates/deployer-db.png
Normal file
|
After Width: | Height: | Size: 497 KiB |
BIN
src/assets/templates/devise-db-dark.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
src/assets/templates/devise-db.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
src/assets/templates/django-axes-db-dark.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
src/assets/templates/django-axes-db.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
src/assets/templates/doorkeeper-db-dark.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
src/assets/templates/doorkeeper-db.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
src/assets/templates/feedbin-db-dark.png
Normal file
|
After Width: | Height: | Size: 498 KiB |
BIN
src/assets/templates/feedbin-db.png
Normal file
|
After Width: | Height: | Size: 583 KiB |
BIN
src/assets/templates/flarum-db-dark.png
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
src/assets/templates/flarum-db.png
Normal file
|
After Width: | Height: | Size: 499 KiB |
BIN
src/assets/templates/flipper-db-dark.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
src/assets/templates/flipper-db.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
src/assets/templates/freescout-db-dark.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
src/assets/templates/freescout-db.png
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
src/assets/templates/hacker-news-db-dark.png
Normal file
|
After Width: | Height: | Size: 427 KiB |
BIN
src/assets/templates/hacker-news-db.png
Normal file
|
After Width: | Height: | Size: 472 KiB |
BIN
src/assets/templates/laravel-activitylog-db-dark.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
src/assets/templates/laravel-activitylog-db.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
src/assets/templates/octobox-db-dark.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
src/assets/templates/octobox-db.png
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
src/assets/templates/orchid-db-dark.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
src/assets/templates/orchid-db.png
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
src/assets/templates/pay-rails-db-dark.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
src/assets/templates/pay-rails-db.png
Normal file
|
After Width: | Height: | Size: 371 KiB |
BIN
src/assets/templates/pixelfed-db-dark.png
Normal file
|
After Width: | Height: | Size: 593 KiB |
BIN
src/assets/templates/pixelfed-db.png
Normal file
|
After Width: | Height: | Size: 687 KiB |
BIN
src/assets/templates/polr-db-dark.png
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
src/assets/templates/polr-db.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
src/assets/templates/reversion-db-dark.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
src/assets/templates/reversion-db.png
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
src/assets/templates/screeenly-db-dark.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
src/assets/templates/screeenly-db.png
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
src/assets/templates/staytus-db-dark.png
Normal file
|
After Width: | Height: | Size: 424 KiB |
BIN
src/assets/templates/staytus-db.png
Normal file
|
After Width: | Height: | Size: 471 KiB |
BIN
src/assets/templates/taggit-db-dark.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
src/assets/templates/taggit-db.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
src/assets/templates/talk-db-dark.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
src/assets/templates/talk-db.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
@@ -3,6 +3,7 @@ import React, { lazy, Suspense, useCallback, useEffect } from 'react';
|
|||||||
import { Spinner } from '../spinner/spinner';
|
import { Spinner } from '../spinner/spinner';
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
import { useMonaco } from '@monaco-editor/react';
|
import { useMonaco } from '@monaco-editor/react';
|
||||||
|
import { useToast } from '@/components/toast/use-toast';
|
||||||
import { Button } from '../button/button';
|
import { Button } from '../button/button';
|
||||||
import { Copy, CopyCheck } from 'lucide-react';
|
import { Copy, CopyCheck } from 'lucide-react';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
||||||
@@ -11,6 +12,14 @@ import { DarkTheme } from './themes/dark';
|
|||||||
import { LightTheme } from './themes/light';
|
import { LightTheme } from './themes/light';
|
||||||
import './config.ts';
|
import './config.ts';
|
||||||
|
|
||||||
|
export const Editor = lazy(() =>
|
||||||
|
import('./code-editor').then((module) => ({
|
||||||
|
default: module.Editor,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
type EditorType = typeof Editor;
|
||||||
|
|
||||||
export interface CodeSnippetProps {
|
export interface CodeSnippetProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
code: string;
|
code: string;
|
||||||
@@ -18,14 +27,9 @@ export interface CodeSnippetProps {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
autoScroll?: boolean;
|
autoScroll?: boolean;
|
||||||
isComplete?: boolean;
|
isComplete?: boolean;
|
||||||
|
editorProps?: React.ComponentProps<EditorType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Editor = lazy(() =>
|
|
||||||
import('./code-editor').then((module) => ({
|
|
||||||
default: module.Editor,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||||
({
|
({
|
||||||
className,
|
className,
|
||||||
@@ -34,10 +38,12 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
language = 'sql',
|
language = 'sql',
|
||||||
autoScroll = false,
|
autoScroll = false,
|
||||||
isComplete = true,
|
isComplete = true,
|
||||||
|
editorProps,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const monaco = useMonaco();
|
const monaco = useMonaco();
|
||||||
const { effectiveTheme } = useTheme();
|
const { effectiveTheme } = useTheme();
|
||||||
|
const { toast } = useToast();
|
||||||
const [isCopied, setIsCopied] = React.useState(false);
|
const [isCopied, setIsCopied] = React.useState(false);
|
||||||
const [tooltipOpen, setTooltipOpen] = React.useState(false);
|
const [tooltipOpen, setTooltipOpen] = React.useState(false);
|
||||||
|
|
||||||
@@ -66,10 +72,32 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
}
|
}
|
||||||
}, [code, monaco, autoScroll]);
|
}, [code, monaco, autoScroll]);
|
||||||
|
|
||||||
const copyToClipboard = useCallback(() => {
|
const copyToClipboard = useCallback(async () => {
|
||||||
navigator.clipboard.writeText(code);
|
if (!navigator?.clipboard) {
|
||||||
setIsCopied(true);
|
toast({
|
||||||
}, [code]);
|
title: t('copy_to_clipboard_toast.unsupported.title'),
|
||||||
|
variant: 'destructive',
|
||||||
|
description: t(
|
||||||
|
'copy_to_clipboard_toast.unsupported.description'
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
setIsCopied(true);
|
||||||
|
} catch {
|
||||||
|
setIsCopied(false);
|
||||||
|
toast({
|
||||||
|
title: t('copy_to_clipboard_toast.failed.title'),
|
||||||
|
variant: 'destructive',
|
||||||
|
description: t(
|
||||||
|
'copy_to_clipboard_toast.failed.description'
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [code, t, toast]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -120,27 +148,32 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
language={language}
|
language={language}
|
||||||
loading={<Spinner />}
|
loading={<Spinner />}
|
||||||
theme={effectiveTheme}
|
theme={effectiveTheme}
|
||||||
|
{...editorProps}
|
||||||
options={{
|
options={{
|
||||||
minimap: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
scrollbar: {
|
|
||||||
vertical: 'hidden',
|
|
||||||
horizontal: 'hidden',
|
|
||||||
alwaysConsumeMouseWheel: false,
|
|
||||||
},
|
|
||||||
scrollBeyondLastLine: false,
|
scrollBeyondLastLine: false,
|
||||||
renderValidationDecorations: 'off',
|
renderValidationDecorations: 'off',
|
||||||
lineDecorationsWidth: 0,
|
lineDecorationsWidth: 0,
|
||||||
overviewRulerBorder: false,
|
overviewRulerBorder: false,
|
||||||
overviewRulerLanes: 0,
|
overviewRulerLanes: 0,
|
||||||
hideCursorInOverviewRuler: true,
|
hideCursorInOverviewRuler: true,
|
||||||
|
contextmenu: false,
|
||||||
|
...editorProps?.options,
|
||||||
guides: {
|
guides: {
|
||||||
indentation: false,
|
indentation: false,
|
||||||
|
...editorProps?.options?.guides,
|
||||||
|
},
|
||||||
|
scrollbar: {
|
||||||
|
vertical: 'hidden',
|
||||||
|
horizontal: 'hidden',
|
||||||
|
alwaysConsumeMouseWheel: false,
|
||||||
|
...editorProps?.options?.scrollbar,
|
||||||
|
},
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
...editorProps?.options?.minimap,
|
||||||
},
|
},
|
||||||
contextmenu: false,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!isComplete ? (
|
{!isComplete ? (
|
||||||
|
|||||||
54
src/components/code-snippet/languages/dbml-language.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { Monaco } from '@monaco-editor/react';
|
||||||
|
import { dataTypes } from '@/lib/data/data-types/data-types';
|
||||||
|
|
||||||
|
export const setupDBMLLanguage = (monaco: Monaco) => {
|
||||||
|
monaco.languages.register({ id: 'dbml' });
|
||||||
|
|
||||||
|
// Define themes for DBML
|
||||||
|
monaco.editor.defineTheme('dbml-dark', {
|
||||||
|
base: 'vs-dark',
|
||||||
|
inherit: true,
|
||||||
|
rules: [
|
||||||
|
{ token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords
|
||||||
|
{ token: 'string', foreground: 'CE9178' }, // Strings
|
||||||
|
{ token: 'annotation', foreground: '9CDCFE' }, // [annotations]
|
||||||
|
{ token: 'delimiter', foreground: 'D4D4D4' }, // Braces {}
|
||||||
|
{ token: 'operator', foreground: 'D4D4D4' }, // Operators
|
||||||
|
{ token: 'datatype', foreground: '4EC9B0' }, // Data types
|
||||||
|
],
|
||||||
|
colors: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
monaco.editor.defineTheme('dbml-light', {
|
||||||
|
base: 'vs',
|
||||||
|
inherit: true,
|
||||||
|
rules: [
|
||||||
|
{ token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords
|
||||||
|
{ token: 'string', foreground: 'A31515' }, // Strings
|
||||||
|
{ token: 'annotation', foreground: '001080' }, // [annotations]
|
||||||
|
{ token: 'delimiter', foreground: '000000' }, // Braces {}
|
||||||
|
{ token: 'operator', foreground: '000000' }, // Operators
|
||||||
|
{ token: 'type', foreground: '267F99' }, // Data types
|
||||||
|
],
|
||||||
|
colors: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataTypesNames = dataTypes.map((dt) => dt.name);
|
||||||
|
const datatypePattern = dataTypesNames.join('|');
|
||||||
|
|
||||||
|
monaco.languages.setMonarchTokensProvider('dbml', {
|
||||||
|
keywords: ['Table', 'Ref', 'Indexes'],
|
||||||
|
datatypes: dataTypesNames,
|
||||||
|
tokenizer: {
|
||||||
|
root: [
|
||||||
|
[/\b(Table|Ref|Indexes)\b/, 'keyword'],
|
||||||
|
[/\[.*?\]/, 'annotation'],
|
||||||
|
[/".*?"/, 'string'],
|
||||||
|
[/'.*?'/, 'string'],
|
||||||
|
[/[{}]/, 'delimiter'],
|
||||||
|
[/[<>]/, 'operator'],
|
||||||
|
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
|
||||||
|
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
import {
|
import {
|
||||||
databaseEditionToImageMap,
|
databaseEditionToImageMap,
|
||||||
databaseEditionToLabelMap,
|
databaseEditionToLabelMap,
|
||||||
@@ -9,39 +9,44 @@ import {
|
|||||||
databaseSecondaryLogoMap,
|
databaseSecondaryLogoMap,
|
||||||
databaseTypeToLabelMap,
|
databaseTypeToLabelMap,
|
||||||
} from '@/lib/databases';
|
} from '@/lib/databases';
|
||||||
|
import type { DatabaseType } from '@/lib/domain/database-type';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export interface DiagramIconProps {
|
export interface DiagramIconProps
|
||||||
diagram: Diagram;
|
extends React.ComponentPropsWithoutRef<'div'> {
|
||||||
|
databaseType: DatabaseType;
|
||||||
|
databaseEdition?: DatabaseEdition;
|
||||||
|
imgClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DiagramIcon = React.forwardRef<
|
export const DiagramIcon = React.forwardRef<
|
||||||
React.ElementRef<typeof TooltipTrigger>,
|
React.ElementRef<typeof TooltipTrigger>,
|
||||||
DiagramIconProps
|
DiagramIconProps
|
||||||
>(({ diagram }, ref) =>
|
>(({ databaseType, databaseEdition, className, imgClassName }, ref) =>
|
||||||
diagram.databaseEdition ? (
|
databaseEdition ? (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger className="mr-1" ref={ref}>
|
<TooltipTrigger className={cn('mr-1', className)} ref={ref} asChild>
|
||||||
<img
|
<img
|
||||||
src={databaseEditionToImageMap[diagram.databaseEdition]}
|
src={databaseEditionToImageMap[databaseEdition]}
|
||||||
className="h-5 max-w-fit rounded-full"
|
className={cn('h-5 max-w-fit rounded-full', imgClassName)}
|
||||||
alt="database"
|
alt="database"
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{databaseEditionToLabelMap[diagram.databaseEdition]}
|
{databaseEditionToLabelMap[databaseEdition]}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger className="mr-2" ref={ref}>
|
<TooltipTrigger className={cn('mr-2', className)} ref={ref} asChild>
|
||||||
<img
|
<img
|
||||||
src={databaseSecondaryLogoMap[diagram.databaseType]}
|
src={databaseSecondaryLogoMap[databaseType]}
|
||||||
className="h-5 max-w-fit"
|
className={cn('h-5 max-w-fit', imgClassName)}
|
||||||
alt="database"
|
alt="database"
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{databaseTypeToLabelMap[diagram.databaseType]}
|
{databaseTypeToLabelMap[databaseType]}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|||||||
import { Cross2Icon } from '@radix-ui/react-icons';
|
import { Cross2Icon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { ScrollArea } from '../scroll-area/scroll-area';
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root;
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
@@ -110,6 +111,21 @@ const DialogDescription = React.forwardRef<
|
|||||||
));
|
));
|
||||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
const DialogInternalContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollArea>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollArea>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ScrollArea
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-1 max-h-screen flex-col overflow-y-auto',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogInternalContent.displayName = 'DialogInternalContent';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
@@ -121,4 +137,5 @@ export {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
DialogInternalContent,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +1,66 @@
|
|||||||
import React, { forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
import EmptyStateImage from '@/assets/empty_state.png';
|
import EmptyStateImage from '@/assets/empty_state.png';
|
||||||
|
import EmptyStateImageDark from '@/assets/empty_state_dark.png';
|
||||||
import { Label } from '@/components/label/label';
|
import { Label } from '@/components/label/label';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
|
|
||||||
export interface EmptyStateProps {
|
export interface EmptyStateProps {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
imageClassName?: string;
|
||||||
|
titleClassName?: string;
|
||||||
|
descriptionClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyState = forwardRef<
|
export const EmptyState = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement> & EmptyStateProps
|
React.HTMLAttributes<HTMLDivElement> & EmptyStateProps
|
||||||
>(({ title, description, className }, ref) => (
|
>(
|
||||||
<div
|
(
|
||||||
ref={ref}
|
{
|
||||||
className={cn(
|
title,
|
||||||
'flex flex-1 flex-col items-center justify-center space-y-1',
|
description,
|
||||||
className
|
className,
|
||||||
)}
|
titleClassName,
|
||||||
>
|
descriptionClassName,
|
||||||
<img src={EmptyStateImage} alt="Empty state" className="w-32" />
|
imageClassName,
|
||||||
<Label className="text-base">{title}</Label>
|
},
|
||||||
<Label className="text-sm font-normal text-muted-foreground">
|
ref
|
||||||
{description}
|
) => {
|
||||||
</Label>
|
const { effectiveTheme } = useTheme();
|
||||||
</div>
|
|
||||||
));
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-1 flex-col items-center justify-center space-y-1',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
effectiveTheme === 'dark'
|
||||||
|
? EmptyStateImageDark
|
||||||
|
: EmptyStateImage
|
||||||
|
}
|
||||||
|
alt="Empty state"
|
||||||
|
className={cn('mb-2 w-20', imageClassName)}
|
||||||
|
/>
|
||||||
|
<Label className={cn('text-base', titleClassName)}>
|
||||||
|
{title}
|
||||||
|
</Label>
|
||||||
|
<Label
|
||||||
|
className={cn(
|
||||||
|
'text-sm font-normal text-muted-foreground',
|
||||||
|
descriptionClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
EmptyState.displayName = 'EmptyState';
|
EmptyState.displayName = 'EmptyState';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const ScrollArea = React.forwardRef<
|
|||||||
className={cn('relative overflow-hidden', className)}
|
className={cn('relative overflow-hidden', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport className="scrollable-flex size-full rounded-[inherit]">
|
<ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]">
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
@@ -40,7 +40,7 @@ const ScrollBar = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative z-20 flex-1 rounded-full bg-border" />
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
));
|
));
|
||||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type ToasterToast = ToastProps & {
|
|||||||
layout?: 'row' | 'column';
|
layout?: 'row' | 'column';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const actionTypes = {
|
const actionTypes = {
|
||||||
ADD_TOAST: 'ADD_TOAST',
|
ADD_TOAST: 'ADD_TOAST',
|
||||||
UPDATE_TOAST: 'UPDATE_TOAST',
|
UPDATE_TOAST: 'UPDATE_TOAST',
|
||||||
|
|||||||
15
src/context/alert-context/alert-context.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
import { emptyFn } from '@/lib/utils';
|
||||||
|
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
||||||
|
|
||||||
|
export interface AlertContext {
|
||||||
|
showAlert: (params: BaseAlertDialogProps) => void;
|
||||||
|
closeAlert: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const alertContext = createContext<AlertContext>({
|
||||||
|
closeAlert: emptyFn,
|
||||||
|
showAlert: emptyFn,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useAlert = () => useContext(alertContext);
|
||||||
36
src/context/alert-context/alert-provider.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import type { AlertContext } from './alert-context';
|
||||||
|
import { alertContext } from './alert-context';
|
||||||
|
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
||||||
|
import { BaseAlertDialog } from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
||||||
|
|
||||||
|
export const AlertProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const [showAlert, setShowAlert] = useState(false);
|
||||||
|
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const showAlertHandler: AlertContext['showAlert'] = useCallback(
|
||||||
|
(params) => {
|
||||||
|
setAlertParams(params);
|
||||||
|
setShowAlert(true);
|
||||||
|
},
|
||||||
|
[setShowAlert, setAlertParams]
|
||||||
|
);
|
||||||
|
const closeAlertHandler = useCallback(() => {
|
||||||
|
setShowAlert(false);
|
||||||
|
}, [setShowAlert]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<alertContext.Provider
|
||||||
|
value={{
|
||||||
|
showAlert: showAlertHandler,
|
||||||
|
closeAlert: closeAlertHandler,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} />
|
||||||
|
</alertContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
22
src/context/canvas-context/canvas-context.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import { emptyFn } from '@/lib/utils';
|
||||||
|
import type { Graph } from '@/lib/graph';
|
||||||
|
import { createGraph } from '@/lib/graph';
|
||||||
|
|
||||||
|
export interface CanvasContext {
|
||||||
|
reorderTables: (options?: { updateHistory?: boolean }) => void;
|
||||||
|
fitView: (options?: {
|
||||||
|
duration?: number;
|
||||||
|
padding?: number;
|
||||||
|
maxZoom?: number;
|
||||||
|
}) => void;
|
||||||
|
setOverlapGraph: (graph: Graph<string>) => void;
|
||||||
|
overlapGraph: Graph<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const canvasContext = createContext<CanvasContext>({
|
||||||
|
reorderTables: emptyFn,
|
||||||
|
fitView: emptyFn,
|
||||||
|
setOverlapGraph: emptyFn,
|
||||||
|
overlapGraph: createGraph(),
|
||||||
|
});
|
||||||
85
src/context/canvas-context/canvas-provider.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import React, { type ReactNode, useCallback, useState } from 'react';
|
||||||
|
import { canvasContext } from './canvas-context';
|
||||||
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import {
|
||||||
|
adjustTablePositions,
|
||||||
|
shouldShowTablesBySchemaFilter,
|
||||||
|
} from '@/lib/domain/db-table';
|
||||||
|
import { useReactFlow } from '@xyflow/react';
|
||||||
|
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
|
||||||
|
import type { Graph } from '@/lib/graph';
|
||||||
|
import { createGraph } from '@/lib/graph';
|
||||||
|
|
||||||
|
interface CanvasProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||||
|
const { tables, relationships, updateTablesState, filteredSchemas } =
|
||||||
|
useChartDB();
|
||||||
|
const { fitView } = useReactFlow();
|
||||||
|
const [overlapGraph, setOverlapGraph] =
|
||||||
|
useState<Graph<string>>(createGraph());
|
||||||
|
|
||||||
|
const reorderTables = useCallback(
|
||||||
|
(
|
||||||
|
options: { updateHistory?: boolean } = {
|
||||||
|
updateHistory: true,
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const newTables = adjustTablePositions({
|
||||||
|
relationships,
|
||||||
|
tables: tables.filter((table) =>
|
||||||
|
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
||||||
|
),
|
||||||
|
mode: 'all', // Use 'all' mode for manual reordering
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedOverlapGraph = findOverlappingTables({
|
||||||
|
tables: newTables,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateTablesState(
|
||||||
|
(currentTables) =>
|
||||||
|
currentTables.map((table) => {
|
||||||
|
const newTable = newTables.find(
|
||||||
|
(t) => t.id === table.id
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: table.id,
|
||||||
|
x: newTable?.x ?? table.x,
|
||||||
|
y: newTable?.y ?? table.y,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
updateHistory: options.updateHistory ?? true,
|
||||||
|
forceOverride: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
setOverlapGraph(updatedOverlapGraph);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
fitView({
|
||||||
|
duration: 500,
|
||||||
|
padding: 0.2,
|
||||||
|
maxZoom: 0.8,
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
[filteredSchemas, relationships, tables, updateTablesState, fitView]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvasContext.Provider
|
||||||
|
value={{
|
||||||
|
reorderTables,
|
||||||
|
fitView,
|
||||||
|
setOverlapGraph,
|
||||||
|
overlapGraph,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</canvasContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -84,6 +84,7 @@ export interface ChartDBContext {
|
|||||||
options?: { updateHistory: boolean }
|
options?: { updateHistory: boolean }
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
loadDiagram: (diagramId: string) => Promise<Diagram | undefined>;
|
loadDiagram: (diagramId: string) => Promise<Diagram | undefined>;
|
||||||
|
loadDiagramFromData: (diagram: Diagram) => void;
|
||||||
updateDiagramUpdatedAt: () => Promise<void>;
|
updateDiagramUpdatedAt: () => Promise<void>;
|
||||||
clearDiagramData: () => Promise<void>;
|
clearDiagramData: () => Promise<void>;
|
||||||
deleteDiagram: () => Promise<void>;
|
deleteDiagram: () => Promise<void>;
|
||||||
@@ -246,6 +247,7 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
updateDiagramName: emptyFn,
|
updateDiagramName: emptyFn,
|
||||||
updateDiagramUpdatedAt: emptyFn,
|
updateDiagramUpdatedAt: emptyFn,
|
||||||
loadDiagram: emptyFn,
|
loadDiagram: emptyFn,
|
||||||
|
loadDiagramFromData: emptyFn,
|
||||||
clearDiagramData: emptyFn,
|
clearDiagramData: emptyFn,
|
||||||
deleteDiagram: emptyFn,
|
deleteDiagram: emptyFn,
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface ChartDBProviderProps {
|
|||||||
diagram?: Diagram;
|
diagram?: Diagram;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChartDBProvider: React.FC<
|
export const ChartDBProvider: React.FC<
|
||||||
React.PropsWithChildren<ChartDBProviderProps>
|
React.PropsWithChildren<ChartDBProviderProps>
|
||||||
> = ({ children, diagram, readonly }) => {
|
> = ({ children, diagram, readonly }) => {
|
||||||
@@ -310,6 +311,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
color: randomColor(),
|
color: randomColor(),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
isView: false,
|
isView: false,
|
||||||
|
order: tables.length,
|
||||||
...attributes,
|
...attributes,
|
||||||
};
|
};
|
||||||
await addTable(table);
|
await addTable(table);
|
||||||
@@ -1334,15 +1336,9 @@ export const ChartDBProvider: React.FC<
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
|
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
|
||||||
async (diagramId: string) => {
|
useCallback(
|
||||||
const diagram = await db.getDiagram(diagramId, {
|
async (diagram) => {
|
||||||
includeRelationships: true,
|
|
||||||
includeTables: true,
|
|
||||||
includeDependencies: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (diagram) {
|
|
||||||
setDiagramId(diagram.id);
|
setDiagramId(diagram.id);
|
||||||
setDiagramName(diagram.name);
|
setDiagramName(diagram.name);
|
||||||
setDatabaseType(diagram.databaseType);
|
setDatabaseType(diagram.databaseType);
|
||||||
@@ -1354,23 +1350,36 @@ export const ChartDBProvider: React.FC<
|
|||||||
setDiagramUpdatedAt(diagram.updatedAt);
|
setDiagramUpdatedAt(diagram.updatedAt);
|
||||||
|
|
||||||
events.emit({ action: 'load_diagram', data: { diagram } });
|
events.emit({ action: 'load_diagram', data: { diagram } });
|
||||||
|
},
|
||||||
|
[
|
||||||
|
setDiagramId,
|
||||||
|
setDiagramName,
|
||||||
|
setDatabaseType,
|
||||||
|
setDatabaseEdition,
|
||||||
|
setTables,
|
||||||
|
setRelationships,
|
||||||
|
setDependencies,
|
||||||
|
setDiagramCreatedAt,
|
||||||
|
setDiagramUpdatedAt,
|
||||||
|
events,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
|
||||||
|
async (diagramId: string) => {
|
||||||
|
const diagram = await db.getDiagram(diagramId, {
|
||||||
|
includeRelationships: true,
|
||||||
|
includeTables: true,
|
||||||
|
includeDependencies: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (diagram) {
|
||||||
|
loadDiagramFromData(diagram);
|
||||||
}
|
}
|
||||||
|
|
||||||
return diagram;
|
return diagram;
|
||||||
},
|
},
|
||||||
[
|
[db, loadDiagramFromData]
|
||||||
db,
|
|
||||||
setDiagramId,
|
|
||||||
setDiagramName,
|
|
||||||
setDatabaseType,
|
|
||||||
setDatabaseEdition,
|
|
||||||
setTables,
|
|
||||||
setRelationships,
|
|
||||||
setDependencies,
|
|
||||||
setDiagramCreatedAt,
|
|
||||||
setDiagramUpdatedAt,
|
|
||||||
events,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1391,6 +1400,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
updateDiagramId,
|
updateDiagramId,
|
||||||
updateDiagramName,
|
updateDiagramName,
|
||||||
loadDiagram,
|
loadDiagram,
|
||||||
|
loadDiagramFromData,
|
||||||
updateDatabaseType,
|
updateDatabaseType,
|
||||||
updateDatabaseEdition,
|
updateDatabaseEdition,
|
||||||
clearDiagramData,
|
clearDiagramData,
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { emptyFn } from '@/lib/utils';
|
import { emptyFn } from '@/lib/utils';
|
||||||
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
|
||||||
import type { TableSchemaDialogProps } from '@/dialogs/table-schema-dialog/table-schema-dialog';
|
import type { TableSchemaDialogProps } from '@/dialogs/table-schema-dialog/table-schema-dialog';
|
||||||
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
||||||
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
||||||
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
||||||
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-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 { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
||||||
|
|
||||||
export interface DialogContext {
|
export interface DialogContext {
|
||||||
// Create diagram dialog
|
// Create diagram dialog
|
||||||
@@ -14,19 +16,19 @@ export interface DialogContext {
|
|||||||
closeCreateDiagramDialog: () => void;
|
closeCreateDiagramDialog: () => void;
|
||||||
|
|
||||||
// Open diagram dialog
|
// Open diagram dialog
|
||||||
openOpenDiagramDialog: () => void;
|
openOpenDiagramDialog: (
|
||||||
|
params?: Omit<OpenDiagramDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
closeOpenDiagramDialog: () => void;
|
closeOpenDiagramDialog: () => void;
|
||||||
|
|
||||||
// Export SQL dialog
|
// Export SQL dialog
|
||||||
openExportSQLDialog: (params: Omit<ExportSQLDialogProps, 'dialog'>) => void;
|
openExportSQLDialog: (params: Omit<ExportSQLDialogProps, 'dialog'>) => void;
|
||||||
closeExportSQLDialog: () => void;
|
closeExportSQLDialog: () => void;
|
||||||
|
|
||||||
// Alert dialog
|
|
||||||
showAlert: (params: BaseAlertDialogProps) => void;
|
|
||||||
closeAlert: () => void;
|
|
||||||
|
|
||||||
// Create relationship dialog
|
// Create relationship dialog
|
||||||
openCreateRelationshipDialog: () => void;
|
openCreateRelationshipDialog: (
|
||||||
|
params?: Omit<CreateRelationshipDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
closeCreateRelationshipDialog: () => void;
|
closeCreateRelationshipDialog: () => void;
|
||||||
|
|
||||||
// Import database dialog
|
// Import database dialog
|
||||||
@@ -45,6 +47,10 @@ export interface DialogContext {
|
|||||||
openStarUsDialog: () => void;
|
openStarUsDialog: () => void;
|
||||||
closeStarUsDialog: () => void;
|
closeStarUsDialog: () => void;
|
||||||
|
|
||||||
|
// Buckle dialog
|
||||||
|
openBuckleDialog: () => void;
|
||||||
|
closeBuckleDialog: () => void;
|
||||||
|
|
||||||
// Export image dialog
|
// Export image dialog
|
||||||
openExportImageDialog: (
|
openExportImageDialog: (
|
||||||
params: Omit<ExportImageDialogProps, 'dialog'>
|
params: Omit<ExportImageDialogProps, 'dialog'>
|
||||||
@@ -62,6 +68,12 @@ export interface DialogContext {
|
|||||||
params: Omit<ImportDiagramDialogProps, 'dialog'>
|
params: Omit<ImportDiagramDialogProps, 'dialog'>
|
||||||
) => void;
|
) => void;
|
||||||
closeImportDiagramDialog: () => void;
|
closeImportDiagramDialog: () => void;
|
||||||
|
|
||||||
|
// Import DBML dialog
|
||||||
|
openImportDBMLDialog: (
|
||||||
|
params?: Omit<ImportDBMLDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
|
closeImportDBMLDialog: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dialogContext = createContext<DialogContext>({
|
export const dialogContext = createContext<DialogContext>({
|
||||||
@@ -71,8 +83,6 @@ export const dialogContext = createContext<DialogContext>({
|
|||||||
closeOpenDiagramDialog: emptyFn,
|
closeOpenDiagramDialog: emptyFn,
|
||||||
openExportSQLDialog: emptyFn,
|
openExportSQLDialog: emptyFn,
|
||||||
closeExportSQLDialog: emptyFn,
|
closeExportSQLDialog: emptyFn,
|
||||||
closeAlert: emptyFn,
|
|
||||||
showAlert: emptyFn,
|
|
||||||
closeCreateRelationshipDialog: emptyFn,
|
closeCreateRelationshipDialog: emptyFn,
|
||||||
openCreateRelationshipDialog: emptyFn,
|
openCreateRelationshipDialog: emptyFn,
|
||||||
openImportDatabaseDialog: emptyFn,
|
openImportDatabaseDialog: emptyFn,
|
||||||
@@ -87,4 +97,8 @@ export const dialogContext = createContext<DialogContext>({
|
|||||||
closeExportDiagramDialog: emptyFn,
|
closeExportDiagramDialog: emptyFn,
|
||||||
openImportDiagramDialog: emptyFn,
|
openImportDiagramDialog: emptyFn,
|
||||||
closeImportDiagramDialog: emptyFn,
|
closeImportDiagramDialog: emptyFn,
|
||||||
|
openBuckleDialog: emptyFn,
|
||||||
|
closeBuckleDialog: emptyFn,
|
||||||
|
openImportDBMLDialog: emptyFn,
|
||||||
|
closeImportDBMLDialog: emptyFn,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ 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 { 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 { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
||||||
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
||||||
import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||||
import { BaseAlertDialog } from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
|
||||||
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||||
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
||||||
import { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
|
import { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
|
||||||
@@ -19,16 +19,42 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
|
|||||||
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
|
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
|
||||||
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
||||||
|
import { BuckleDialog } from '@/dialogs/buckle-dialog/buckle-dialog';
|
||||||
|
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
||||||
|
import { ImportDBMLDialog } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
||||||
|
|
||||||
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false);
|
const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false);
|
||||||
const [openOpenDiagramDialog, setOpenOpenDiagramDialog] = useState(false);
|
const [openOpenDiagramDialog, setOpenOpenDiagramDialog] = useState(false);
|
||||||
|
const [openDiagramDialogParams, setOpenDiagramDialogParams] =
|
||||||
|
useState<Omit<OpenDiagramDialogProps, 'dialog'>>();
|
||||||
|
|
||||||
|
const openOpenDiagramDialogHandler: DialogContext['openOpenDiagramDialog'] =
|
||||||
|
useCallback(
|
||||||
|
(props) => {
|
||||||
|
setOpenDiagramDialogParams(props);
|
||||||
|
setOpenOpenDiagramDialog(true);
|
||||||
|
},
|
||||||
|
[setOpenOpenDiagramDialog]
|
||||||
|
);
|
||||||
|
|
||||||
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
|
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [createRelationshipDialogParams, setCreateRelationshipDialogParams] =
|
||||||
|
useState<Omit<CreateRelationshipDialogProps, 'dialog'>>();
|
||||||
|
const openCreateRelationshipDialogHandler: DialogContext['openCreateRelationshipDialog'] =
|
||||||
|
useCallback(
|
||||||
|
(params) => {
|
||||||
|
setCreateRelationshipDialogParams(params);
|
||||||
|
setOpenCreateRelationshipDialog(true);
|
||||||
|
},
|
||||||
|
[setOpenCreateRelationshipDialog]
|
||||||
|
);
|
||||||
|
|
||||||
const [openStarUsDialog, setOpenStarUsDialog] = useState(false);
|
const [openStarUsDialog, setOpenStarUsDialog] = useState(false);
|
||||||
|
const [openBuckleDialog, setOpenBuckleDialog] = useState(false);
|
||||||
|
|
||||||
// Export image dialog
|
// Export image dialog
|
||||||
const [openExportImageDialog, setOpenExportImageDialog] = useState(false);
|
const [openExportImageDialog, setOpenExportImageDialog] = useState(false);
|
||||||
@@ -88,7 +114,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[setOpenTableSchemaDialog]
|
[setOpenTableSchemaDialog]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Export image dialog
|
// Export diagram dialog
|
||||||
const [openExportDiagramDialog, setOpenExportDiagramDialog] =
|
const [openExportDiagramDialog, setOpenExportDiagramDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
@@ -96,35 +122,22 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
|
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
// Alert dialog
|
// Import DBML dialog
|
||||||
const [showAlert, setShowAlert] = useState(false);
|
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
|
||||||
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
|
const [importDBMLDialogParams, setImportDBMLDialogParams] =
|
||||||
title: '',
|
useState<Omit<ImportDBMLDialogProps, 'dialog'>>();
|
||||||
});
|
|
||||||
const showAlertHandler: DialogContext['showAlert'] = useCallback(
|
|
||||||
(params) => {
|
|
||||||
setAlertParams(params);
|
|
||||||
setShowAlert(true);
|
|
||||||
},
|
|
||||||
[setShowAlert, setAlertParams]
|
|
||||||
);
|
|
||||||
const closeAlertHandler = useCallback(() => {
|
|
||||||
setShowAlert(false);
|
|
||||||
}, [setShowAlert]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialogContext.Provider
|
<dialogContext.Provider
|
||||||
value={{
|
value={{
|
||||||
openCreateDiagramDialog: () => setOpenNewDiagramDialog(true),
|
openCreateDiagramDialog: () => setOpenNewDiagramDialog(true),
|
||||||
closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false),
|
closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false),
|
||||||
openOpenDiagramDialog: () => setOpenOpenDiagramDialog(true),
|
openOpenDiagramDialog: openOpenDiagramDialogHandler,
|
||||||
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
|
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
|
||||||
openExportSQLDialog: openExportSQLDialogHandler,
|
openExportSQLDialog: openExportSQLDialogHandler,
|
||||||
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
|
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
|
||||||
showAlert: showAlertHandler,
|
openCreateRelationshipDialog:
|
||||||
closeAlert: closeAlertHandler,
|
openCreateRelationshipDialogHandler,
|
||||||
openCreateRelationshipDialog: () =>
|
|
||||||
setOpenCreateRelationshipDialog(true),
|
|
||||||
closeCreateRelationshipDialog: () =>
|
closeCreateRelationshipDialog: () =>
|
||||||
setOpenCreateRelationshipDialog(false),
|
setOpenCreateRelationshipDialog(false),
|
||||||
openImportDatabaseDialog: openImportDatabaseDialogHandler,
|
openImportDatabaseDialog: openImportDatabaseDialogHandler,
|
||||||
@@ -134,6 +147,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
closeTableSchemaDialog: () => setOpenTableSchemaDialog(false),
|
closeTableSchemaDialog: () => setOpenTableSchemaDialog(false),
|
||||||
openStarUsDialog: () => setOpenStarUsDialog(true),
|
openStarUsDialog: () => setOpenStarUsDialog(true),
|
||||||
closeStarUsDialog: () => setOpenStarUsDialog(false),
|
closeStarUsDialog: () => setOpenStarUsDialog(false),
|
||||||
|
closeBuckleDialog: () => setOpenBuckleDialog(false),
|
||||||
|
openBuckleDialog: () => setOpenBuckleDialog(true),
|
||||||
closeExportImageDialog: () => setOpenExportImageDialog(false),
|
closeExportImageDialog: () => setOpenExportImageDialog(false),
|
||||||
openExportImageDialog: openExportImageDialogHandler,
|
openExportImageDialog: openExportImageDialogHandler,
|
||||||
openExportDiagramDialog: () => setOpenExportDiagramDialog(true),
|
openExportDiagramDialog: () => setOpenExportDiagramDialog(true),
|
||||||
@@ -142,18 +157,26 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
|
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
|
||||||
closeImportDiagramDialog: () =>
|
closeImportDiagramDialog: () =>
|
||||||
setOpenImportDiagramDialog(false),
|
setOpenImportDiagramDialog(false),
|
||||||
|
openImportDBMLDialog: (params) => {
|
||||||
|
setImportDBMLDialogParams(params);
|
||||||
|
setOpenImportDBMLDialog(true);
|
||||||
|
},
|
||||||
|
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<CreateDiagramDialog dialog={{ open: openNewDiagramDialog }} />
|
<CreateDiagramDialog dialog={{ open: openNewDiagramDialog }} />
|
||||||
<OpenDiagramDialog dialog={{ open: openOpenDiagramDialog }} />
|
<OpenDiagramDialog
|
||||||
|
dialog={{ open: openOpenDiagramDialog }}
|
||||||
|
{...openDiagramDialogParams}
|
||||||
|
/>
|
||||||
<ExportSQLDialog
|
<ExportSQLDialog
|
||||||
dialog={{ open: openExportSQLDialog }}
|
dialog={{ open: openExportSQLDialog }}
|
||||||
{...exportSQLDialogParams}
|
{...exportSQLDialogParams}
|
||||||
/>
|
/>
|
||||||
<BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} />
|
|
||||||
<CreateRelationshipDialog
|
<CreateRelationshipDialog
|
||||||
dialog={{ open: openCreateRelationshipDialog }}
|
dialog={{ open: openCreateRelationshipDialog }}
|
||||||
|
{...createRelationshipDialogParams}
|
||||||
/>
|
/>
|
||||||
<ImportDatabaseDialog
|
<ImportDatabaseDialog
|
||||||
dialog={{ open: openImportDatabaseDialog }}
|
dialog={{ open: openImportDatabaseDialog }}
|
||||||
@@ -170,6 +193,11 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
/>
|
/>
|
||||||
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
|
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
|
||||||
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
|
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
|
||||||
|
<BuckleDialog dialog={{ open: openBuckleDialog }} />
|
||||||
|
<ImportDBMLDialog
|
||||||
|
dialog={{ open: openImportDBMLDialog }}
|
||||||
|
{...importDBMLDialogParams}
|
||||||
|
/>
|
||||||
</dialogContext.Provider>
|
</dialogContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo, useEffect, useState } from 'react';
|
||||||
import type { ExportImageContext, ImageType } from './export-image-context';
|
import type { ExportImageContext, ImageType } from './export-image-context';
|
||||||
import { exportImageContext } from './export-image-context';
|
import { exportImageContext } from './export-image-context';
|
||||||
import { toJpeg, toPng, toSvg } from 'html-to-image';
|
import { toJpeg, toPng, toSvg } from 'html-to-image';
|
||||||
@@ -6,6 +6,8 @@ import { useReactFlow } from '@xyflow/react';
|
|||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
|
import logoDark from '@/assets/logo-dark.png';
|
||||||
|
import logoLight from '@/assets/logo-light.png';
|
||||||
|
|
||||||
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -14,6 +16,24 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const { setNodes, getViewport } = useReactFlow();
|
const { setNodes, getViewport } = useReactFlow();
|
||||||
const { effectiveTheme } = useTheme();
|
const { effectiveTheme } = useTheme();
|
||||||
const { diagramName } = useChartDB();
|
const { diagramName } = useChartDB();
|
||||||
|
const [logoBase64, setLogoBase64] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Convert logo to base64 on component mount
|
||||||
|
const img = new Image();
|
||||||
|
img.src = effectiveTheme === 'light' ? logoLight : logoDark;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const base64 = canvas.toDataURL('image/png');
|
||||||
|
setLogoBase64(base64);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [effectiveTheme]);
|
||||||
|
|
||||||
const downloadImage = useCallback(
|
const downloadImage = useCallback(
|
||||||
(dataUrl: string, type: ImageType) => {
|
(dataUrl: string, type: ImageType) => {
|
||||||
@@ -128,16 +148,22 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
'http://www.w3.org/2000/svg',
|
'http://www.w3.org/2000/svg',
|
||||||
'rect'
|
'rect'
|
||||||
);
|
);
|
||||||
const padding = 2000;
|
const bgPadding = 2000;
|
||||||
backgroundRect.setAttribute('x', String(-viewport.x - padding));
|
backgroundRect.setAttribute(
|
||||||
backgroundRect.setAttribute('y', String(-viewport.y - padding));
|
'x',
|
||||||
|
String(-viewport.x - bgPadding)
|
||||||
|
);
|
||||||
|
backgroundRect.setAttribute(
|
||||||
|
'y',
|
||||||
|
String(-viewport.y - bgPadding)
|
||||||
|
);
|
||||||
backgroundRect.setAttribute(
|
backgroundRect.setAttribute(
|
||||||
'width',
|
'width',
|
||||||
String(reactFlowBounds.width + 2 * padding)
|
String(reactFlowBounds.width + 2 * bgPadding)
|
||||||
);
|
);
|
||||||
backgroundRect.setAttribute(
|
backgroundRect.setAttribute(
|
||||||
'height',
|
'height',
|
||||||
String(reactFlowBounds.height + 2 * padding)
|
String(reactFlowBounds.height + 2 * bgPadding)
|
||||||
);
|
);
|
||||||
backgroundRect.setAttribute('fill', 'url(#background-pattern)');
|
backgroundRect.setAttribute('fill', 'url(#background-pattern)');
|
||||||
tempSvg.appendChild(backgroundRect);
|
tempSvg.appendChild(backgroundRect);
|
||||||
@@ -148,27 +174,110 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dataUrl = await imageCreateFn(viewportElement, {
|
// Handle SVG export differently
|
||||||
...(type === 'jpeg' || type === 'png'
|
if (type === 'svg') {
|
||||||
? {
|
const dataUrl = await imageCreateFn(viewportElement, {
|
||||||
backgroundColor:
|
width: reactFlowBounds.width,
|
||||||
effectiveTheme === 'light'
|
height: reactFlowBounds.height,
|
||||||
? '#ffffff'
|
style: {
|
||||||
: '#141414',
|
width: `${reactFlowBounds.width}px`,
|
||||||
}
|
height: `${reactFlowBounds.height}px`,
|
||||||
: {}),
|
transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`,
|
||||||
width: reactFlowBounds.width,
|
},
|
||||||
height: reactFlowBounds.height,
|
quality: 1,
|
||||||
style: {
|
pixelRatio: scale,
|
||||||
width: `${reactFlowBounds.width}px`,
|
skipFonts: true,
|
||||||
height: `${reactFlowBounds.height}px`,
|
});
|
||||||
transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`,
|
downloadImage(dataUrl, type);
|
||||||
},
|
return;
|
||||||
quality: 1,
|
}
|
||||||
pixelRatio: scale,
|
|
||||||
});
|
|
||||||
|
|
||||||
downloadImage(dataUrl, type);
|
// For PNG and JPEG, continue with the watermark process
|
||||||
|
const initialDataUrl = await imageCreateFn(
|
||||||
|
viewportElement,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
effectiveTheme === 'light'
|
||||||
|
? '#ffffff'
|
||||||
|
: '#141414',
|
||||||
|
width: reactFlowBounds.width,
|
||||||
|
height: reactFlowBounds.height,
|
||||||
|
style: {
|
||||||
|
width: `${reactFlowBounds.width}px`,
|
||||||
|
height: `${reactFlowBounds.height}px`,
|
||||||
|
transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`,
|
||||||
|
},
|
||||||
|
quality: 1,
|
||||||
|
pixelRatio: scale,
|
||||||
|
skipFonts: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a canvas to combine the diagram and watermark
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
downloadImage(initialDataUrl, type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set canvas size to match the export size
|
||||||
|
canvas.width = reactFlowBounds.width * scale;
|
||||||
|
canvas.height = reactFlowBounds.height * scale;
|
||||||
|
|
||||||
|
// Load the exported diagram
|
||||||
|
const diagramImage = new Image();
|
||||||
|
diagramImage.src = initialDataUrl;
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
diagramImage.onload = async () => {
|
||||||
|
// Draw the diagram
|
||||||
|
ctx.drawImage(diagramImage, 0, 0);
|
||||||
|
|
||||||
|
// Calculate logo size
|
||||||
|
const logoHeight = Math.max(
|
||||||
|
24,
|
||||||
|
Math.floor(canvas.width * 0.024)
|
||||||
|
);
|
||||||
|
const padding = Math.max(
|
||||||
|
12,
|
||||||
|
Math.floor(logoHeight * 0.5)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load and draw the logo
|
||||||
|
const logoImage = new Image();
|
||||||
|
logoImage.src = logoBase64;
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
logoImage.onload = () => {
|
||||||
|
// Calculate logo width while maintaining aspect ratio
|
||||||
|
const logoWidth =
|
||||||
|
(logoImage.width / logoImage.height) *
|
||||||
|
logoHeight;
|
||||||
|
|
||||||
|
// Draw logo in bottom-left corner
|
||||||
|
ctx.globalAlpha = 0.9;
|
||||||
|
ctx.drawImage(
|
||||||
|
logoImage,
|
||||||
|
padding,
|
||||||
|
canvas.height - logoHeight - padding,
|
||||||
|
logoWidth,
|
||||||
|
logoHeight
|
||||||
|
);
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
resolve(null);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert canvas to data URL
|
||||||
|
const finalDataUrl = canvas.toDataURL(
|
||||||
|
type === 'png' ? 'image/png' : 'image/jpeg'
|
||||||
|
);
|
||||||
|
downloadImage(finalDataUrl, type);
|
||||||
|
resolve(null);
|
||||||
|
};
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
viewportElement.removeChild(tempSvg);
|
viewportElement.removeChild(tempSvg);
|
||||||
hideLoader();
|
hideLoader();
|
||||||
@@ -183,6 +292,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setNodes,
|
setNodes,
|
||||||
showLoader,
|
showLoader,
|
||||||
effectiveTheme,
|
effectiveTheme,
|
||||||
|
logoBase64,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
import { useHistory } from '@/hooks/use-history';
|
import { useHistory } from '@/hooks/use-history';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
|
import { useReactFlow } from '@xyflow/react';
|
||||||
|
|
||||||
export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -15,6 +17,9 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const { redo, undo } = useHistory();
|
const { redo, undo } = useHistory();
|
||||||
const { openOpenDiagramDialog } = useDialog();
|
const { openOpenDiagramDialog } = useDialog();
|
||||||
const { updateDiagramUpdatedAt } = useChartDB();
|
const { updateDiagramUpdatedAt } = useChartDB();
|
||||||
|
const { toggleSidePanel } = useLayout();
|
||||||
|
const { fitView } = useReactFlow();
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
|
keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
|
||||||
redo,
|
redo,
|
||||||
@@ -34,7 +39,7 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
keyboardShortcutsForOS[KeyboardShortcutAction.OPEN_DIAGRAM]
|
keyboardShortcutsForOS[KeyboardShortcutAction.OPEN_DIAGRAM]
|
||||||
.keyCombination,
|
.keyCombination,
|
||||||
openOpenDiagramDialog,
|
() => openOpenDiagramDialog(),
|
||||||
{
|
{
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
@@ -49,6 +54,29 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
},
|
},
|
||||||
[updateDiagramUpdatedAt]
|
[updateDiagramUpdatedAt]
|
||||||
);
|
);
|
||||||
|
useHotkeys(
|
||||||
|
keyboardShortcutsForOS[KeyboardShortcutAction.TOGGLE_SIDE_PANEL]
|
||||||
|
.keyCombination,
|
||||||
|
toggleSidePanel,
|
||||||
|
{
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[toggleSidePanel]
|
||||||
|
);
|
||||||
|
useHotkeys(
|
||||||
|
keyboardShortcutsForOS[KeyboardShortcutAction.SHOW_ALL].keyCombination,
|
||||||
|
() => {
|
||||||
|
fitView({
|
||||||
|
duration: 500,
|
||||||
|
padding: 0.1,
|
||||||
|
maxZoom: 0.8,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[fitView]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<keyboardShortcutsContext.Provider value={{}}>
|
<keyboardShortcutsContext.Provider value={{}}>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export enum KeyboardShortcutAction {
|
|||||||
UNDO = 'undo',
|
UNDO = 'undo',
|
||||||
OPEN_DIAGRAM = 'open_diagram',
|
OPEN_DIAGRAM = 'open_diagram',
|
||||||
SAVE_DIAGRAM = 'save_diagram',
|
SAVE_DIAGRAM = 'save_diagram',
|
||||||
|
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
|
||||||
|
SHOW_ALL = 'show_all',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyboardShortcut {
|
export interface KeyboardShortcut {
|
||||||
@@ -47,6 +49,20 @@ export const keyboardShortcuts: Record<
|
|||||||
keyCombinationMac: 'meta+s',
|
keyCombinationMac: 'meta+s',
|
||||||
keyCombinationWin: 'ctrl+s',
|
keyCombinationWin: 'ctrl+s',
|
||||||
},
|
},
|
||||||
|
[KeyboardShortcutAction.TOGGLE_SIDE_PANEL]: {
|
||||||
|
action: KeyboardShortcutAction.TOGGLE_SIDE_PANEL,
|
||||||
|
keyCombinationLabelMac: '⌘B',
|
||||||
|
keyCombinationLabelWin: 'Ctrl+B',
|
||||||
|
keyCombinationMac: 'meta+b',
|
||||||
|
keyCombinationWin: 'ctrl+b',
|
||||||
|
},
|
||||||
|
[KeyboardShortcutAction.SHOW_ALL]: {
|
||||||
|
action: KeyboardShortcutAction.SHOW_ALL,
|
||||||
|
keyCombinationLabelMac: '⌘0',
|
||||||
|
keyCombinationLabelWin: 'Ctrl+0',
|
||||||
|
keyCombinationMac: 'meta+0',
|
||||||
|
keyCombinationWin: 'ctrl+0',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface KeyboardShortcutForOS {
|
export interface KeyboardShortcutForOS {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface LayoutContext {
|
|||||||
isSidePanelShowed: boolean;
|
isSidePanelShowed: boolean;
|
||||||
hideSidePanel: () => void;
|
hideSidePanel: () => void;
|
||||||
showSidePanel: () => void;
|
showSidePanel: () => void;
|
||||||
|
toggleSidePanel: () => void;
|
||||||
|
|
||||||
isSelectSchemaOpen: boolean;
|
isSelectSchemaOpen: boolean;
|
||||||
openSelectSchema: () => void;
|
openSelectSchema: () => void;
|
||||||
@@ -47,6 +48,7 @@ export const layoutContext = createContext<LayoutContext>({
|
|||||||
isSidePanelShowed: false,
|
isSidePanelShowed: false,
|
||||||
hideSidePanel: emptyFn,
|
hideSidePanel: emptyFn,
|
||||||
showSidePanel: emptyFn,
|
showSidePanel: emptyFn,
|
||||||
|
toggleSidePanel: emptyFn,
|
||||||
|
|
||||||
isSelectSchemaOpen: false,
|
isSelectSchemaOpen: false,
|
||||||
openSelectSchema: emptyFn,
|
openSelectSchema: emptyFn,
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const showSidePanel: LayoutContext['showSidePanel'] = () =>
|
const showSidePanel: LayoutContext['showSidePanel'] = () =>
|
||||||
setIsSidePanelShowed(true);
|
setIsSidePanelShowed(true);
|
||||||
|
|
||||||
|
const toggleSidePanel: LayoutContext['toggleSidePanel'] = () => {
|
||||||
|
setIsSidePanelShowed((prevIsSidePanelShowed) => !prevIsSidePanelShowed);
|
||||||
|
};
|
||||||
|
|
||||||
const openTableFromSidebar: LayoutContext['openTableFromSidebar'] = (
|
const openTableFromSidebar: LayoutContext['openTableFromSidebar'] = (
|
||||||
tableId
|
tableId
|
||||||
) => {
|
) => {
|
||||||
@@ -77,6 +81,7 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
isSidePanelShowed,
|
isSidePanelShowed,
|
||||||
hideSidePanel,
|
hideSidePanel,
|
||||||
showSidePanel,
|
showSidePanel,
|
||||||
|
toggleSidePanel,
|
||||||
isSelectSchemaOpen,
|
isSelectSchemaOpen,
|
||||||
openSelectSchema,
|
openSelectSchema,
|
||||||
closeSelectSchema,
|
closeSelectSchema,
|
||||||
|
|||||||
@@ -30,8 +30,17 @@ export interface LocalConfigContext {
|
|||||||
starUsDialogLastOpen: number;
|
starUsDialogLastOpen: number;
|
||||||
setStarUsDialogLastOpen: (lastOpen: number) => void;
|
setStarUsDialogLastOpen: (lastOpen: number) => void;
|
||||||
|
|
||||||
|
buckleWaitlistOpened: boolean;
|
||||||
|
setBuckleWaitlistOpened: (githubRepoOpened: boolean) => void;
|
||||||
|
|
||||||
|
buckleDialogLastOpen: number;
|
||||||
|
setBuckleDialogLastOpen: (lastOpen: number) => void;
|
||||||
|
|
||||||
showDependenciesOnCanvas: boolean;
|
showDependenciesOnCanvas: boolean;
|
||||||
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
|
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
|
||||||
|
|
||||||
|
showMiniMapOnCanvas: boolean;
|
||||||
|
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LocalConfigContext = createContext<LocalConfigContext>({
|
export const LocalConfigContext = createContext<LocalConfigContext>({
|
||||||
@@ -56,6 +65,15 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
|||||||
starUsDialogLastOpen: 0,
|
starUsDialogLastOpen: 0,
|
||||||
setStarUsDialogLastOpen: emptyFn,
|
setStarUsDialogLastOpen: emptyFn,
|
||||||
|
|
||||||
|
buckleWaitlistOpened: false,
|
||||||
|
setBuckleWaitlistOpened: emptyFn,
|
||||||
|
|
||||||
|
buckleDialogLastOpen: 0,
|
||||||
|
setBuckleDialogLastOpen: emptyFn,
|
||||||
|
|
||||||
showDependenciesOnCanvas: false,
|
showDependenciesOnCanvas: false,
|
||||||
setShowDependenciesOnCanvas: emptyFn,
|
setShowDependenciesOnCanvas: emptyFn,
|
||||||
|
|
||||||
|
showMiniMapOnCanvas: false,
|
||||||
|
setShowMiniMapOnCanvas: emptyFn,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ const showCardinalityKey = 'show_cardinality';
|
|||||||
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
|
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
|
||||||
const githubRepoOpenedKey = 'github_repo_opened';
|
const githubRepoOpenedKey = 'github_repo_opened';
|
||||||
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
||||||
|
const buckleWaitlistOpenedKey = 'buckle_waitlist_opened';
|
||||||
|
const buckleDialogLastOpenKey = 'buckle_dialog_last_open';
|
||||||
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
|
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
|
||||||
|
const showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
|
||||||
|
|
||||||
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -48,12 +51,28 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
|
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [buckleWaitlistOpened, setBuckleWaitlistOpened] =
|
||||||
|
React.useState<boolean>(
|
||||||
|
(localStorage.getItem(buckleWaitlistOpenedKey) || 'false') ===
|
||||||
|
'true'
|
||||||
|
);
|
||||||
|
|
||||||
|
const [buckleDialogLastOpen, setBuckleDialogLastOpen] =
|
||||||
|
React.useState<number>(
|
||||||
|
parseInt(localStorage.getItem(buckleDialogLastOpenKey) || '0')
|
||||||
|
);
|
||||||
|
|
||||||
const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] =
|
const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] =
|
||||||
React.useState<boolean>(
|
React.useState<boolean>(
|
||||||
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
|
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
|
||||||
'true'
|
'true'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
|
||||||
|
React.useState<boolean>(
|
||||||
|
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
starUsDialogLastOpenKey,
|
starUsDialogLastOpenKey,
|
||||||
@@ -65,6 +84,20 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
||||||
}, [githubRepoOpened]);
|
}, [githubRepoOpened]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem(
|
||||||
|
buckleDialogLastOpenKey,
|
||||||
|
buckleDialogLastOpen.toString()
|
||||||
|
);
|
||||||
|
}, [buckleDialogLastOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem(
|
||||||
|
buckleWaitlistOpenedKey,
|
||||||
|
buckleWaitlistOpened.toString()
|
||||||
|
);
|
||||||
|
}, [buckleWaitlistOpened]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
hideMultiSchemaNotificationKey,
|
hideMultiSchemaNotificationKey,
|
||||||
@@ -95,6 +128,13 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
);
|
);
|
||||||
}, [showDependenciesOnCanvas]);
|
}, [showDependenciesOnCanvas]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem(
|
||||||
|
showMiniMapOnCanvasKey,
|
||||||
|
showMiniMapOnCanvas.toString()
|
||||||
|
);
|
||||||
|
}, [showMiniMapOnCanvas]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalConfigContext.Provider
|
<LocalConfigContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -114,6 +154,12 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setStarUsDialogLastOpen,
|
setStarUsDialogLastOpen,
|
||||||
showDependenciesOnCanvas,
|
showDependenciesOnCanvas,
|
||||||
setShowDependenciesOnCanvas,
|
setShowDependenciesOnCanvas,
|
||||||
|
setBuckleDialogLastOpen,
|
||||||
|
buckleDialogLastOpen,
|
||||||
|
buckleWaitlistOpened,
|
||||||
|
setBuckleWaitlistOpened,
|
||||||
|
showMiniMapOnCanvas,
|
||||||
|
setShowMiniMapOnCanvas,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -122,6 +122,32 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
config: '++id, defaultDiagramId',
|
config: '++id, defaultDiagramId',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.version(8).stores({
|
||||||
|
diagrams:
|
||||||
|
'++id, name, databaseType, databaseEdition, createdAt, updatedAt',
|
||||||
|
db_tables:
|
||||||
|
'++id, diagramId, name, schema, x, y, fields, indexes, color, createdAt, width, comment, isView, isMaterializedView, order',
|
||||||
|
db_relationships:
|
||||||
|
'++id, diagramId, name, sourceSchema, sourceTableId, targetSchema, targetTableId, sourceFieldId, targetFieldId, type, createdAt',
|
||||||
|
db_dependencies:
|
||||||
|
'++id, diagramId, schema, tableId, dependentSchema, dependentTableId, createdAt',
|
||||||
|
config: '++id, defaultDiagramId',
|
||||||
|
});
|
||||||
|
|
||||||
|
db.version(9).upgrade((tx) =>
|
||||||
|
tx
|
||||||
|
.table<DBTable & { diagramId: string }>('db_tables')
|
||||||
|
.toCollection()
|
||||||
|
.modify((table) => {
|
||||||
|
for (const field of table.fields) {
|
||||||
|
if (typeof field.nullable === 'string') {
|
||||||
|
field.nullable =
|
||||||
|
(field.nullable as string).toLowerCase() === 'true';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
db.on('ready', async () => {
|
db.on('ready', async () => {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
|
|
||||||
@@ -270,6 +296,23 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
attributes: Partial<Diagram>;
|
attributes: Partial<Diagram>;
|
||||||
}) => {
|
}) => {
|
||||||
await db.diagrams.update(id, attributes);
|
await db.diagrams.update(id, attributes);
|
||||||
|
|
||||||
|
if (attributes.id) {
|
||||||
|
await Promise.all([
|
||||||
|
db.db_tables
|
||||||
|
.where('diagramId')
|
||||||
|
.equals(id)
|
||||||
|
.modify({ diagramId: attributes.id }),
|
||||||
|
db.db_relationships
|
||||||
|
.where('diagramId')
|
||||||
|
.equals(id)
|
||||||
|
.modify({ diagramId: attributes.id }),
|
||||||
|
db.db_dependencies
|
||||||
|
.where('diagramId')
|
||||||
|
.equals(id)
|
||||||
|
.modify({ diagramId: attributes.id }),
|
||||||
|
]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteDiagram: StorageContext['deleteDiagram'] = async (
|
const deleteDiagram: StorageContext['deleteDiagram'] = async (
|
||||||
@@ -345,15 +388,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
.equals(diagramId)
|
.equals(diagramId)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
// Sort tables first alphabetically, then views alphabetically
|
return tables;
|
||||||
return tables.sort((a, b) => {
|
|
||||||
if (a.isView === b.isView) {
|
|
||||||
// Both are either tables or views, so sort alphabetically by name
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
}
|
|
||||||
// If one is a view and the other is not, put tables first
|
|
||||||
return a.isView ? 1 : -1;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addRelationship: StorageContext['addRelationship'] = async ({
|
const addRelationship: StorageContext['addRelationship'] = async ({
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@/components/alert-dialog/alert-dialog';
|
} from '@/components/alert-dialog/alert-dialog';
|
||||||
import type { AlertDialogProps } from '@radix-ui/react-alert-dialog';
|
import type { AlertDialogProps } from '@radix-ui/react-alert-dialog';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useAlert } from '@/context/alert-context/alert-context';
|
||||||
|
|
||||||
export interface BaseAlertDialogProps {
|
export interface BaseAlertDialogProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -33,7 +33,7 @@ export const BaseAlertDialog: React.FC<BaseAlertDialogProps> = ({
|
|||||||
content,
|
content,
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { closeAlert } = useDialog();
|
const { closeAlert } = useAlert();
|
||||||
|
|
||||||
const closeAlertHandler = useCallback(() => {
|
const closeAlertHandler = useCallback(() => {
|
||||||
onClose?.();
|
onClose?.();
|
||||||
|
|||||||
80
src/dialogs/buckle-dialog/buckle-dialog.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/dialog/dialog';
|
||||||
|
import { Button } from '@/components/button/button';
|
||||||
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
|
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||||
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
|
|
||||||
|
export interface BuckleDialogProps extends BaseDialogProps {}
|
||||||
|
|
||||||
|
export const BuckleDialog: React.FC<BuckleDialogProps> = ({ dialog }) => {
|
||||||
|
const { setBuckleWaitlistOpened } = useLocalConfig();
|
||||||
|
const { effectiveTheme } = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dialog.open) return;
|
||||||
|
}, [dialog.open]);
|
||||||
|
const { closeBuckleDialog } = useDialog();
|
||||||
|
|
||||||
|
const handleConfirm = useCallback(() => {
|
||||||
|
setBuckleWaitlistOpened(true);
|
||||||
|
window.open('https://waitlist.buckle.dev', '_blank');
|
||||||
|
}, [setBuckleWaitlistOpened]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
{...dialog}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
closeBuckleDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent
|
||||||
|
className="flex flex-col"
|
||||||
|
showClose={false}
|
||||||
|
onInteractOutside={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="hidden" />
|
||||||
|
<DialogDescription className="hidden" />
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex w-full flex-col items-center">
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
effectiveTheme === 'light'
|
||||||
|
? '/buckle-animated.gif'
|
||||||
|
: '/buckle.png'
|
||||||
|
}
|
||||||
|
className="h-16"
|
||||||
|
/>
|
||||||
|
<div className="mt-6 text-center text-base">
|
||||||
|
We've been working on something big -{' '}
|
||||||
|
<span className="font-semibold">Ready to explore?</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter className="flex gap-1 md:justify-between">
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button variant="secondary">Not now</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button onClick={handleConfirm}>
|
||||||
|
Try ChartDB v2.0!
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogInternalContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/dialog/dialog';
|
} from '@/components/dialog/dialog';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
||||||
@@ -84,6 +85,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
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);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadScripts = async () => {
|
const loadScripts = async () => {
|
||||||
const { importMetadataScripts } = await import(
|
const { importMetadataScripts } = await import(
|
||||||
@@ -126,6 +129,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
const inputValue = e.target.value;
|
const inputValue = e.target.value;
|
||||||
setScriptResult(inputValue);
|
setScriptResult(inputValue);
|
||||||
|
|
||||||
|
// Automatically open SSMS info when input length is exactly 65535
|
||||||
|
if (inputValue.length === 65535) {
|
||||||
|
setShowSSMSInfoDialog(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setScriptResult]
|
[setScriptResult]
|
||||||
);
|
);
|
||||||
@@ -139,6 +147,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
setScriptResult(fixedJson);
|
setScriptResult(fixedJson);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
} else {
|
} else {
|
||||||
|
setScriptResult(fixedJson);
|
||||||
setErrorMessage(errorScriptOutputMessage);
|
setErrorMessage(errorScriptOutputMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,188 +166,204 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
|
|
||||||
const renderContent = useCallback(() => {
|
const renderContent = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-1 flex-col gap-6">
|
<DialogInternalContent>
|
||||||
{databaseTypeToEditionMap[databaseType].length > 0 ? (
|
<div className="flex w-full flex-1 flex-col gap-6">
|
||||||
<div className="flex flex-col gap-1 md:flex-row">
|
{databaseTypeToEditionMap[databaseType].length > 0 ? (
|
||||||
<p className="text-sm leading-6 text-muted-foreground">
|
<div className="flex flex-col gap-1 md:flex-row">
|
||||||
{t(
|
<p className="text-sm leading-6 text-muted-foreground">
|
||||||
'new_diagram_dialog.import_database.database_edition'
|
{t(
|
||||||
)}
|
'new_diagram_dialog.import_database.database_edition'
|
||||||
</p>
|
)}
|
||||||
<ToggleGroup
|
</p>
|
||||||
type="single"
|
<ToggleGroup
|
||||||
className="ml-1 flex-wrap gap-2"
|
type="single"
|
||||||
value={
|
className="ml-1 flex-wrap gap-2"
|
||||||
!databaseEdition ? 'regular' : databaseEdition
|
value={
|
||||||
}
|
!databaseEdition
|
||||||
onValueChange={(value) => {
|
? 'regular'
|
||||||
setDatabaseEdition(
|
: databaseEdition
|
||||||
value === 'regular'
|
}
|
||||||
? undefined
|
onValueChange={(value) => {
|
||||||
: (value as DatabaseEdition)
|
setDatabaseEdition(
|
||||||
);
|
value === 'regular'
|
||||||
}}
|
? undefined
|
||||||
>
|
: (value as DatabaseEdition)
|
||||||
<ToggleGroupItem
|
);
|
||||||
value="regular"
|
}}
|
||||||
variant="outline"
|
|
||||||
className="h-6 gap-1 p-0 px-2 shadow-none"
|
|
||||||
>
|
>
|
||||||
<Avatar className="size-4 rounded-none">
|
<ToggleGroupItem
|
||||||
<AvatarImage
|
value="regular"
|
||||||
src={
|
variant="outline"
|
||||||
databaseSecondaryLogoMap[
|
className="h-6 gap-1 p-0 px-2 shadow-none"
|
||||||
databaseType
|
>
|
||||||
]
|
<Avatar className="size-4 rounded-none">
|
||||||
}
|
<AvatarImage
|
||||||
alt="Regular"
|
src={
|
||||||
/>
|
databaseSecondaryLogoMap[
|
||||||
<AvatarFallback>Regular</AvatarFallback>
|
databaseType
|
||||||
</Avatar>
|
]
|
||||||
Regular
|
}
|
||||||
</ToggleGroupItem>
|
alt="Regular"
|
||||||
{databaseTypeToEditionMap[databaseType].map(
|
/>
|
||||||
(edition) => (
|
<AvatarFallback>Regular</AvatarFallback>
|
||||||
<ToggleGroupItem
|
</Avatar>
|
||||||
value={edition}
|
Regular
|
||||||
key={edition}
|
</ToggleGroupItem>
|
||||||
variant="outline"
|
{databaseTypeToEditionMap[databaseType].map(
|
||||||
className="h-6 gap-1 p-0 px-2 shadow-none"
|
(edition) => (
|
||||||
>
|
<ToggleGroupItem
|
||||||
<Avatar className="size-4">
|
value={edition}
|
||||||
<AvatarImage
|
key={edition}
|
||||||
src={
|
variant="outline"
|
||||||
databaseEditionToImageMap[
|
className="h-6 gap-1 p-0 px-2 shadow-none"
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
alt={
|
|
||||||
databaseEditionToLabelMap[
|
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AvatarFallback>
|
|
||||||
{
|
|
||||||
databaseEditionToLabelMap[
|
|
||||||
edition
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{databaseEditionToLabelMap[edition]}
|
|
||||||
</ToggleGroupItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</ToggleGroup>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
|
|
||||||
<div>
|
|
||||||
1. {t('new_diagram_dialog.import_database.step_1')}
|
|
||||||
</div>
|
|
||||||
{databaseType === DatabaseType.SQL_SERVER && (
|
|
||||||
<SSMSInfo />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{databaseTypeToClientsMap[databaseType].length > 0 ? (
|
|
||||||
<Tabs
|
|
||||||
value={
|
|
||||||
!databaseClient ? 'dbclient' : databaseClient
|
|
||||||
}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setDatabaseClient(
|
|
||||||
value === 'dbclient'
|
|
||||||
? undefined
|
|
||||||
: (value as DatabaseClient)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex flex-1">
|
|
||||||
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
|
|
||||||
<TabsTrigger
|
|
||||||
value="dbclient"
|
|
||||||
className="h-6 w-20"
|
|
||||||
>
|
|
||||||
DB Client
|
|
||||||
</TabsTrigger>
|
|
||||||
|
|
||||||
{databaseClients?.map((client) => (
|
|
||||||
<TabsTrigger
|
|
||||||
key={client}
|
|
||||||
value={client}
|
|
||||||
className="h-6 !w-20"
|
|
||||||
>
|
>
|
||||||
{databaseClientToLabelMap[client]}
|
<Avatar className="size-4">
|
||||||
</TabsTrigger>
|
<AvatarImage
|
||||||
)) ?? []}
|
src={
|
||||||
</TabsList>
|
databaseEditionToImageMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
alt={
|
||||||
|
databaseEditionToLabelMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AvatarFallback>
|
||||||
|
{
|
||||||
|
databaseEditionToLabelMap[
|
||||||
|
edition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{databaseEditionToLabelMap[edition]}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
|
||||||
|
<div>
|
||||||
|
1.{' '}
|
||||||
|
{t('new_diagram_dialog.import_database.step_1')}
|
||||||
</div>
|
</div>
|
||||||
|
{databaseType === DatabaseType.SQL_SERVER && (
|
||||||
|
<SSMSInfo
|
||||||
|
open={showSSMSInfoDialog}
|
||||||
|
setOpen={setShowSSMSInfoDialog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{databaseTypeToClientsMap[databaseType].length > 0 ? (
|
||||||
|
<Tabs
|
||||||
|
value={
|
||||||
|
!databaseClient
|
||||||
|
? 'dbclient'
|
||||||
|
: databaseClient
|
||||||
|
}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setDatabaseClient(
|
||||||
|
value === 'dbclient'
|
||||||
|
? undefined
|
||||||
|
: (value as DatabaseClient)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-1">
|
||||||
|
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
|
||||||
|
<TabsTrigger
|
||||||
|
value="dbclient"
|
||||||
|
className="h-6 w-20"
|
||||||
|
>
|
||||||
|
DB Client
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
{databaseClients?.map((client) => (
|
||||||
|
<TabsTrigger
|
||||||
|
key={client}
|
||||||
|
value={client}
|
||||||
|
className="h-6 !w-20"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
databaseClientToLabelMap[
|
||||||
|
client
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</TabsTrigger>
|
||||||
|
)) ?? []}
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
<CodeSnippet
|
||||||
|
className="h-40 w-full"
|
||||||
|
loading={!importMetadataScripts}
|
||||||
|
code={
|
||||||
|
importMetadataScripts?.[databaseType]?.(
|
||||||
|
{
|
||||||
|
databaseEdition,
|
||||||
|
databaseClient,
|
||||||
|
}
|
||||||
|
) ?? ''
|
||||||
|
}
|
||||||
|
language={databaseClient ? 'shell' : 'sql'}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
) : (
|
||||||
<CodeSnippet
|
<CodeSnippet
|
||||||
className="h-40 w-full"
|
className="h-40 w-full flex-auto"
|
||||||
loading={!importMetadataScripts}
|
loading={!importMetadataScripts}
|
||||||
code={
|
code={
|
||||||
importMetadataScripts?.[databaseType]?.({
|
importMetadataScripts?.[databaseType]?.({
|
||||||
databaseEdition,
|
databaseEdition,
|
||||||
databaseClient,
|
|
||||||
}) ?? ''
|
}) ?? ''
|
||||||
}
|
}
|
||||||
language={databaseClient ? 'shell' : 'sql'}
|
language="sql"
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
|
||||||
) : (
|
|
||||||
<CodeSnippet
|
|
||||||
className="h-40 w-full flex-auto"
|
|
||||||
loading={!importMetadataScripts}
|
|
||||||
code={
|
|
||||||
importMetadataScripts?.[databaseType]?.({
|
|
||||||
databaseEdition,
|
|
||||||
}) ?? ''
|
|
||||||
}
|
|
||||||
language="sql"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex h-48 flex-col gap-1">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
2. {t('new_diagram_dialog.import_database.step_2')}
|
|
||||||
</p>
|
|
||||||
<Textarea
|
|
||||||
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
|
|
||||||
placeholder={t(
|
|
||||||
'new_diagram_dialog.import_database.script_results_placeholder'
|
|
||||||
)}
|
)}
|
||||||
value={scriptResult}
|
</div>
|
||||||
onChange={handleInputChange}
|
<div className="flex h-48 flex-col gap-1">
|
||||||
/>
|
<p className="text-sm text-muted-foreground">
|
||||||
{showCheckJsonButton || errorMessage ? (
|
2. {t('new_diagram_dialog.import_database.step_2')}
|
||||||
<div className="mt-2 flex items-center gap-2">
|
</p>
|
||||||
{showCheckJsonButton ? (
|
<Textarea
|
||||||
<Button
|
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
|
||||||
type="button"
|
placeholder={t(
|
||||||
variant="outline"
|
'new_diagram_dialog.import_database.script_results_placeholder'
|
||||||
size="sm"
|
|
||||||
onClick={handleCheckJson}
|
|
||||||
disabled={isCheckingJson}
|
|
||||||
>
|
|
||||||
{isCheckingJson ? (
|
|
||||||
<Spinner size="small" />
|
|
||||||
) : (
|
|
||||||
t(
|
|
||||||
'new_diagram_dialog.import_database.check_script_result'
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-red-700">
|
|
||||||
{errorMessage}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
value={scriptResult}
|
||||||
) : null}
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
{showCheckJsonButton || errorMessage ? (
|
||||||
|
<div className="mt-2 flex items-center gap-2">
|
||||||
|
{showCheckJsonButton ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleCheckJson}
|
||||||
|
disabled={isCheckingJson}
|
||||||
|
>
|
||||||
|
{isCheckingJson ? (
|
||||||
|
<Spinner size="small" />
|
||||||
|
) : (
|
||||||
|
t(
|
||||||
|
'new_diagram_dialog.import_database.check_script_result'
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-red-700">
|
||||||
|
{errorMessage}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogInternalContent>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
databaseEdition,
|
databaseEdition,
|
||||||
@@ -354,6 +379,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
showCheckJsonButton,
|
showCheckJsonButton,
|
||||||
isCheckingJson,
|
isCheckingJson,
|
||||||
handleCheckJson,
|
handleCheckJson,
|
||||||
|
showSSMSInfoDialog,
|
||||||
|
setShowSSMSInfoDialog,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderFooter = useCallback(() => {
|
const renderFooter = useCallback(() => {
|
||||||
|
|||||||
@@ -4,32 +4,55 @@ import {
|
|||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from '@/components/hover-card/hover-card';
|
} from '@/components/hover-card/hover-card';
|
||||||
import { Label } from '@/components/label/label';
|
import { Label } from '@/components/label/label';
|
||||||
import { Info } from 'lucide-react';
|
import { Info, X } from 'lucide-react';
|
||||||
import React from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import SSMSInstructions from '@/assets/ssms-instructions.png';
|
import SSMSInstructions from '@/assets/ssms-instructions.png';
|
||||||
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
|
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface SSMSInfoProps {}
|
export interface SSMSInfoProps {
|
||||||
|
open?: boolean;
|
||||||
|
setOpen?: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const SSMSInfo = React.forwardRef<
|
export const SSMSInfo = React.forwardRef<
|
||||||
React.ElementRef<typeof HoverCardTrigger>,
|
React.ElementRef<typeof HoverCardTrigger>,
|
||||||
SSMSInfoProps
|
SSMSInfoProps
|
||||||
>((props, ref) => {
|
>(({ open: controlledOpen, setOpen: setControlledOpen }, ref) => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (controlledOpen) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}, [controlledOpen]);
|
||||||
|
|
||||||
|
const closeHandler = useCallback(() => {
|
||||||
|
setOpen(false);
|
||||||
|
setControlledOpen?.(false);
|
||||||
|
}, [setControlledOpen]);
|
||||||
|
|
||||||
|
const isOpen = useMemo(
|
||||||
|
() => open || controlledOpen,
|
||||||
|
[open, controlledOpen]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard
|
<HoverCard
|
||||||
open={open}
|
open={isOpen}
|
||||||
onOpenChange={(isOpen) => {
|
onOpenChange={(isOpen) => {
|
||||||
|
if (controlledOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setOpen(isOpen);
|
setOpen(isOpen);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HoverCardTrigger ref={ref} {...props} asChild>
|
<HoverCardTrigger ref={ref} asChild>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center gap-1 text-pink-600"
|
className="flex flex-row items-center gap-1 text-pink-600"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(!open);
|
setOpen?.(!open);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Info size={14} />
|
<Info size={14} />
|
||||||
@@ -41,13 +64,21 @@ export const SSMSInfo = React.forwardRef<
|
|||||||
</div>
|
</div>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent className="w-80">
|
<HoverCardContent className="w-80">
|
||||||
<div className="flex">
|
<div className="flex flex-col">
|
||||||
<div className="space-y-1">
|
<div className="flex items-start justify-between">
|
||||||
<h4 className="text-sm font-semibold">
|
<h4 className="text-sm font-semibold">
|
||||||
{t(
|
{t(
|
||||||
'new_diagram_dialog.import_database.ssms_instructions.title'
|
'new_diagram_dialog.import_database.ssms_instructions.title'
|
||||||
)}
|
)}
|
||||||
</h4>
|
</h4>
|
||||||
|
<button
|
||||||
|
onClick={closeHandler}
|
||||||
|
className="text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
<X size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
<span className="font-semibold">1. </span>
|
<span className="font-semibold">1. </span>
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
||||||
DatabaseType.GENERIC
|
DatabaseType.GENERIC
|
||||||
);
|
);
|
||||||
const { closeCreateDiagramDialog } = useDialog();
|
const { closeCreateDiagramDialog, openImportDBMLDialog } = useDialog();
|
||||||
const { updateConfig } = useConfig();
|
const { updateConfig } = useConfig();
|
||||||
const [scriptResult, setScriptResult] = useState('');
|
const [scriptResult, setScriptResult] = useState('');
|
||||||
const [databaseEdition, setDatabaseEdition] = useState<
|
const [databaseEdition, setDatabaseEdition] = useState<
|
||||||
@@ -104,6 +104,10 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
await updateConfig({ defaultDiagramId: diagram.id });
|
await updateConfig({ defaultDiagramId: diagram.id });
|
||||||
closeCreateDiagramDialog();
|
closeCreateDiagramDialog();
|
||||||
navigate(`/diagrams/${diagram.id}`);
|
navigate(`/diagrams/${diagram.id}`);
|
||||||
|
setTimeout(
|
||||||
|
() => openImportDBMLDialog({ withCreateEmptyDiagram: true }),
|
||||||
|
700
|
||||||
|
);
|
||||||
}, [
|
}, [
|
||||||
databaseType,
|
databaseType,
|
||||||
addDiagram,
|
addDiagram,
|
||||||
@@ -112,6 +116,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
navigate,
|
navigate,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
diagramNumber,
|
diagramNumber,
|
||||||
|
openImportDBMLDialog,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -128,7 +133,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]"
|
className="flex max-h-screen w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]"
|
||||||
showClose={hasExistingDiagram}
|
showClose={hasExistingDiagram}
|
||||||
>
|
>
|
||||||
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ export interface ExampleOptionProps {}
|
|||||||
export const ExampleOption: React.FC<ExampleOptionProps> = () => {
|
export const ExampleOption: React.FC<ExampleOptionProps> = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Link href="/examples" className="text-primary hover:text-primary">
|
<Link
|
||||||
<div className="flex size-20 cursor-pointer flex-col items-center rounded-md border py-3 text-center md:size-32">
|
href="/examples"
|
||||||
<div className="flex flex-1 items-center">
|
className="col-span-3 text-primary hover:text-primary"
|
||||||
<LayoutGrid size={34} />
|
>
|
||||||
|
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 rounded-md border py-3 text-center">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<LayoutGrid className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col-reverse">
|
<div className="flex flex-col-reverse">
|
||||||
<div className="hidden text-sm text-primary md:flex">
|
<div className="hidden text-sm text-primary md:flex">
|
||||||
|
|||||||