Compare commits

..

107 Commits

Author SHA1 Message Date
johnnyfish
595e3db0b3 fix(import-db): handle direct query copy-paste instead of fetched JSON 2025-02-17 20:28:15 +02:00
Anthony Mini
ab89bad6d5 fix(i18n): add [FR] translation (#579)
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2025-02-16 20:08:18 +02:00
Jonathan Fishner
deb218423f fix(sqlite-import): import nuallable columns correctly + add json type (#571) 2025-02-16 11:36:02 +02:00
Jonathan Fishner
48342471ac fix(empty-state): show diff buttons on import-dbml when triggered by empty (#574)
* feat(empty-state): trigger import-dbml when clicked empty diagram

* fix(empty-state): show diff buttons on import-dbml when triggered by empty

* fix(empty-state): add missing translations for empty state flow

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-13 17:53:21 +02:00
Guy Ben-Aharon
47bb87a88f chore(main): release 1.8.0 (#558) 2025-02-13 15:49:53 +02:00
Guy Ben-Aharon
a96c2e1078 fix(docker): add option to hide popups (#580) 2025-02-13 15:47:02 +02:00
Guy Ben-Aharon
26d95eed25 fix(table actions): fix size of table actions (#578) 2025-02-11 16:22:54 +02:00
Jonathan Fishner
be65328f24 fix(mssql-import): improve script readability by adding edition comment (#572)
* fix(mssql-import): improve script readability by adding edition comment

* fix(mssql-import): fixing issues with import query
2025-02-10 11:00:51 +02:00
Jonathan Fishner
85fd14fa02 fix(export-sql): show create script for only filtered schemas (#570)
* fix(export-sql): show create script for only filtered schemas

* add dependencies filter

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-10 10:54:22 +02:00
Jonathan Fishner
9c485b3b01 fix(sqlserver-import): open ssms guide when max chars (#565)
* fix(sqlserver-import): open ssms guide when max chars (truncated chars) for input

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-10 10:37:32 +02:00
Jonathan Fishner
e993f1549c fix(canvas): add right-click option to create relationships (#568)
* feat(create-relationship): add right-click option to easy create relationships

* add missing translations

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-10 10:06:31 +02:00
Jonathan Fishner
0db67ea42a fix(import dbml): add import for indexes (#566) 2025-02-10 09:12:11 +02:00
Jonathan Fishner
b9e621bd68 fix(realtionships section): add the schema to source/target tables (#561) 2025-02-09 23:23:11 +02:00
Jonathan Fishner
93d59f8887 fix(import-query): improve the cleanup for messy json input (#562) 2025-02-07 14:03:38 +02:00
Jonathan Fishner
190e4f4ffa feat(dbml-import): add error highlighting for dbml imports (#556)
* feat(dbml-import): add error highlighting for dbml imports

* fix deprecated api

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-06 21:42:14 +02:00
Guy Ben-Aharon
dc404c9d7e fix(canvas): locate table from canvas (#560) 2025-02-06 21:39:55 +02:00
Guy Ben-Aharon
dd4324d64f fix(index unique): extract unique toggle for faster editing (#559) 2025-02-06 21:37:11 +02:00
Calum Siemer
1878083056 feat(docker image): add support for custom inference servers (#543)
- Add OPENAI_API_ENDPOINT configuration
- Add LLM_MODEL_NAME configuration
- Update documentation for custom server setup
- Add error handling for endpoint configuration
2025-02-06 20:16:15 +02:00
Andrii Holovin
7b6271962a fix(i18n): fix Ukrainian (#554) 2025-02-06 18:59:06 +02:00
Guy Ben-Aharon
2edc8dfde8 chore(main): release 1.7.0 (#539) 2025-02-05 12:49:38 +02:00
Guy Ben-Aharon
004d530880 fix(performance): reduce bundle size (#553) 2025-02-03 12:08:02 +02:00
Guy Ben-Aharon
fd2cc9fcfc fix(performance): resolve error on startup (#552) 2025-02-02 17:19:53 +02:00
Guy Ben-Aharon
4c93326bb6 fix(performance): fix bundle size (#551) 2025-02-02 16:45:16 +02:00
Guy Ben-Aharon
ef3d7a8b67 fix(scroll): fix scroll area (#550) 2025-02-02 16:34:18 +02:00
Jonathan Fishner
3b3be086b1 fix(dbml-editor): add shortcuts to dbml and filter: #534 (#535)
* fix(dbml-editor): add shortcuts to dbml and filter

* some fixes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-02 15:36:53 +02:00
Jonathan Fishner
b424518212 feat(import-dbml): add import dbml functionality (#549)
* feat(import-dbml): add import dbml functionality

* fix: fit to view do not run after every tbl move

* fix: add missing translation keys

* some fixes

* some fixes

* fix: update for longer timeout before fitToView

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-02-02 15:20:01 +02:00
Jonathan Fishner
99a8201398 fix(empty-state): fix dark-mode for empty-state (#547) 2025-02-02 11:48:09 +02:00
Jonathan Fishner
eb9b41e4f6 fix(psql-import): remove typo for import command (psql) (#546) 2025-01-30 14:53:01 +02:00
Guy Ben-Aharon
fef6d3f499 fix(dbml): add error handling (#545) 2025-01-28 13:18:54 +02:00
Jonathan Fishner
14f11c27a7 fix(open-diagram): add arrow keys navigation in open diagram dialog (#537)
* fix(open-diagram): add arrow keys navigation in open diagram dialog

* some refining

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-28 13:16:14 +02:00
Guy Ben-Aharon
2118bce0f0 fix(examples): fix employee example dbml (#544) 2025-01-28 09:49:21 +02:00
Jonathan Fishner
88be6c1fd4 feat(dbml-editor): add dbml editor in side pannel (#534)
* add dbml mode

* add dark theme to dbml editor

* add the same sort logic to the dbml

* add comments to dbml

* fix colors + filtering

* fix(dbml-editor): add read-only toast for dbml editor

* update toast location

* fix(translations): add dbml translations

* dbml ui improvements

* dbml ui improvements

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-27 20:32:39 +02:00
Jonathan Fishner
0dcc9b9568 fix(canvas edit): add option to edit names in canvas (#536)
* fix(canvas edit): add option to edit names in canvas

* fix(canvas edit): add option to edit field names in canvas

* some few fixes + style

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-27 14:26:04 +02:00
Andrii Holovin
ff3269ec05 fix(i18n): translation/Ukrainian (#529)
* Update uk.ts

* Apply suggestions from code review

Fix linter issues

---------

Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2025-01-27 12:47:52 +02:00
Guy Ben-Aharon
659dc2e3e7 chore(main): release 1.6.1 (#527)
* chore(main): release 1.6.1

* fix
2025-01-26 19:35:50 +02:00
Jonathan Fishner
c36cd33180 fix(filter-tables) show clean filter if no-results (#532)
* fix(filter-tables) show clean filter if no-results

* add translations

* remove some css classes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-01-26 19:27:35 +02:00
Jonathan Fishner
58231c9139 fix(chat-type): remove typo of char datatype in examples (#530) 2025-01-26 17:43:21 +02:00
Guy Ben-Aharon
1643e7bdeb fix(empty_state): customize empty state (#533) 2025-01-26 14:19:02 +02:00
Guy Ben-Aharon
42d4cbac8c fix: change empty state image (#531) 2025-01-26 13:25:30 +02:00
Jonathan Fishner
7452ca6965 fix(shortcuts): add zoom all shortcut (#528) 2025-01-20 00:26:35 -03:00
Derick Ruiz
27aede7794 Updating the postgres-script so that the psql script creates an output.json with the result instead of using Mac-specific commands. (#520)
Co-authored-by: Jonathan Fishner <jonathanfishner11@gmail.com>
2025-01-14 22:49:40 +02:00
Asgar Aliyev
e9e2736cb2 fix(Image Export): importing css rules error while download image (#524) 2025-01-14 17:39:38 -03:00
Guy Ben-Aharon
74c1730425 chore(main): release 1.6.0 (#512) 2025-01-03 15:41:56 +02:00
Guy Ben-Aharon
94bed7fcce refactor: move menu to a separate component (#517)
* refactor: move menu to a separate component

* merge
2025-01-02 18:11:34 +02:00
Peter
8abf2a7bfc feat(view-menu): add toggle for mini map visibility (#496) (#505)
* feat(view-menu): add toggle for mini map visibility (#496)

* fix build

* fix build

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2025-01-02 17:56:37 +02:00
Guy Ben-Aharon
ee659eaa03 fix: add loadDiagramFromData logic to chartdb provider (#513) 2025-01-01 10:48:42 +02:00
Guy Ben-Aharon
7c5db0848e fix(dependency): upgrade react query to v7 - clean console warnings (#504) 2024-12-31 12:18:58 +02:00
Kerillos Iskandar
4b43f720e9 fix(i18n): translation/Arabic (#509)
* translation/Arabic

* fix lint

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-12-30 18:59:07 +02:00
Kareem Adel
766b5164b8 Add Arabic Language & Enhancements to UI/UX (#507)
* * set nullable option to false
* add visual representing for nullable
* add visual icon for comment
* set showCardinality to true by default
* removed the trash button from the field when hovering for best UX, it's already exist in the context menu

* refactor: improve formatting and structure of TableNodeField component

* ### Translation
- Add Arabic support
- Change languages names sorting for clarity.

### Enhancements
- set the nullable option to false
- add visual representation for nullable
- add a visual icon for comments (show the comment content by hovering)
- set showCardinality to true by default
- removed the trash button from the field when hovering for best UX, it already exists in the context menu

* refactor: standardize string formatting in Arabic locale files

* some alignments

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-12-26 20:00:56 +02:00
Guy Ben-Aharon
7868ca9f42 change last save to icon (#506) 2024-12-26 11:07:50 +02:00
Guy Ben-Aharon
0411742864 Fix multiple dialog pop-ups (#501) 2024-12-24 15:00:55 +02:00
Guy Ben-Aharon
9831ac5a10 add buckle dialog (#499) 2024-12-23 20:56:10 +02:00
Guy Ben-Aharon
91c6fb9249 add buckle dialog (#498) 2024-12-23 20:31:58 +02:00
Guy Ben-Aharon
c155013668 change label (#494) 2024-12-22 23:06:40 +02:00
Guy Ben-Aharon
1b0f293c87 remove diagrams from navbar + buckle (#490) 2024-12-22 19:52:16 +02:00
Guy Ben-Aharon
df2dc03aa0 open diagram dialog resizing (#493) 2024-12-22 16:13:29 +02:00
Guy Ben-Aharon
205d431c89 separate alert dialog from dialog context (#491) 2024-12-22 15:10:42 +02:00
Guy Ben-Aharon
0abe18cdf9 chore(main): release 1.5.1 (#483) 2024-12-22 10:42:44 +02:00
Guy Ben-Aharon
a151f56b5d fix(export): fix SQL server field.nullable type to boolean (#486)
* fix(export): fix SQL server field.nullable type to boolean

* fix build

* fix build
2024-12-15 17:18:57 +02:00
Jonathan Fishner
2b6b733261 fix(readme): Update README.md - add CockroachDB (#482) 2024-12-11 21:12:15 +02:00
Guy Ben-Aharon
b56b04925c chore(main): release 1.5.0 (#469)
* chore(main): release 1.5.0

* fix
2024-12-11 17:25:16 +02:00
Jonathan Fishner
635fb53c9f fix(sql-server import) nullable should be boolean instead of string (#480) 2024-12-11 16:39:40 +02:00
Guy Ben-Aharon
d6659795bc upgrade eslint (#479) 2024-12-11 12:43:45 +02:00
Guy Ben-Aharon
348f80568e fix(core): fix update diagram id (#477) 2024-12-11 00:14:19 +02:00
Guy Ben-Aharon
5f9c74a9ad make the diagram icon component flexible (#476) 2024-12-10 15:22:04 +02:00
Jonathan Fishner
5409288388 feat(CockroachDB): Add CockroachDB support (#472)
* feat(add CockroachDB): Add support in CockroachDB

* remove toggle from toolbar

* fix key

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-12-09 21:46:34 +02:00
Guy Ben-Aharon
2309306ef5 fix(dialogs): fix footer position on dialogs (#470) 2024-12-04 19:44:06 +02:00
Wisnu Wicaksono
3574cecc7c feat(i18n): translate share and dialog sections in Indonesian locale files (#468) 2024-12-04 11:28:40 +02:00
Guy Ben-Aharon
29b8edc051 chore(main): release 1.4.0 (#453) 2024-12-02 13:30:27 +02:00
Huy Bui
5fc10a7e64 fix(clipboard): defensive for navigator clipboard (#462)
* fix: defensive for navigator clipboard

* add dot in en.ts

---------

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

* dynamic import locale base on the selected lang

---------

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

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

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

* update readme to have OPENAI_API_KEY as an env var

* capital

* space

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-26 12:36:55 +02:00
Guy Ben-Aharon
4bb4766e1a fix(docker): make OPENAI_API_KEY optional in docker run (#448) 2024-11-26 12:32:20 +02:00
Guy Ben-Aharon
24db32369a chore(main): release 1.3.0 (#417) 2024-11-25 19:14:51 +02:00
Guy Ben-Aharon
e77ee60a5b fix(i18n): add missing type to vi.ts (#444) 2024-11-25 14:42:14 +02:00
Huy Bui
6c65c2e9cc fix(i18n): add Vietnamese translations (#435) 2024-11-25 13:34:15 +02:00
Huy Bui
70f545f78b feat(side panel): collapsible side panel on desktop view + keyboard shortcut (#439)
* feat: collapsible sidebar on desktop view

* fix: remove unused wrapper

* feat: add toggle side panel hotkey
2024-11-25 13:14:48 +02:00
Jonathan Fishner
fb702c87ce fix(import script): remove double quotes (#442) 2024-11-24 18:25:14 +02:00
Jonathan Fishner
eaa067814f fix(templates): add Five more templates (bouncer, cabot, feedbin, Pythonic, flarum, freescout) (#441)
* feat(add templates): add three more templates (bouncer, cabot, feedbin)

* feat(add templates): add three more templates (Pythonic, flarum, freescout)

* fix build

* fix build
2024-11-24 18:22:50 +02:00
Guy Ben-Aharon
667685ed0f fix(dialogs): fix height of dialogs for small screens (#440)
* fix(dialogs): fix height of dialogs on small screens

* fix type
2024-11-24 12:21:15 +02:00
PRIYANSHI SHARMA
2940431efa fix(i18n): Translating to Gujarati language (#433)
* Translating to gujarati language good first issue #130

* fix build

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-23 19:39:13 +02:00
Guy Ben-Aharon
94ec43b608 fix: fix layout warnings (#434) 2024-11-21 20:30:00 +02:00
Guy Ben-Aharon
a2efed803f lint (#431) 2024-11-21 13:44:30 +02:00
VallabhaE
8749591be0 fix(i18n): Add Telugu Language (#352)
* Adding Telugu Language

* Adding Telugu Language

* revert package.json & package-lock.json

* fix build

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-21 11:35:35 +02:00
Guy Ben-Aharon
c5e0ea6fa4 fix(drawer): set fix min size (#429) 2024-11-20 18:45:32 +02:00
Wisnu Wicaksono
ab07da0b03 fix(i18n): add bahasa indonesia translation (#331)
* feat(i18n): add bahasa indonesia translation

* fix(i18n): update Indonesia name to Bahasa Indonesia in metadata

* fix(i18n): adjust top-navbar & add `id_ID` in `i18n`

* fix build

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2024-11-20 12:11:57 +02:00
☁️dungsil
8397bef392 fix(i18n): update korean for 1.2.0 (#419) 2024-11-19 11:53:06 +02:00
Zer0S2m
7c3c62860e fix(i18n): Translation of the export error into Russian (#418) 2024-11-19 11:45:48 +02:00
Guy Ben-Aharon
76ba4ce4c5 update canonical behavior on templates (#424) 2024-11-19 00:52:48 +02:00
Guy Ben-Aharon
0c0fad719f update canonical behavior on templates (#423) 2024-11-19 00:29:39 +02:00
Jonathan Fishner
b75c6fe4e7 fix(export-sql): add unique to export script (#422) 2024-11-18 21:28:14 +02:00
emircanakpinar
d9fcbeec72 fix(i18n): add Turkish translations (#315)
* fix(i18n): add missing Turkish translations

* fix translation build

---------

Co-authored-by: Guy Ben-Aharon <guybenah@gmail.com>
2024-11-18 16:00:30 +02:00
Guy Ben-Aharon
5d79721b6d update templates tag title (#420) 2024-11-18 14:30:12 +02:00
Guy Ben-Aharon
4be3592cf4 fix(share): fix export to handle broken indexes & relationships (#416) 2024-11-18 13:22:10 +02:00
Guy Ben-Aharon
b4cdcbbbd7 refactor template page - remove helmet data to a new component (#415)
* refactor template page - remove helmet data to a new component

* change texts

---------

Co-authored-by: johnnyfish <jonathanfishner11@gmail.com>
2024-11-18 12:28:04 +02:00
Jonathan Fishner
e9c7f4be06 Update README.md (#413)
* Update README.md

* Update README.md

* Update README.md
2024-11-17 14:14:50 +02:00
210 changed files with 65670 additions and 12451 deletions

View File

@@ -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' },
},
};

View File

@@ -1,5 +1,156 @@
# Changelog
## [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)

View File

@@ -1,6 +1,9 @@
FROM node:22-alpine AS builder
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
@@ -10,15 +13,20 @@ RUN npm ci
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
# Use a lightweight web server to serve the production build
FROM nginx:stable-alpine AS production
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
COPY ./default.conf /etc/nginx/conf.d/default.conf
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
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -16,6 +16,7 @@
<h3 align="center">
<a href="https://discord.gg/QeFwyWSKwC">Community</a> &bull;
<a href="https://www.chartdb.io?ref=github_readme">Website</a> &bull;
<a href="https://chartdb.io/templates?ref=github_readme">Examples</a> &bull;
<a href="https://app.chartdb.io?ref=github_readme">Demo</a>
</h3>
@@ -67,6 +68,7 @@ ChartDB is currently in Public Beta. Star and watch this repository to get notif
- ✅ SQL Server
- ✅ MariaDB
- ✅ SQLite
- ✅ CockroachDB
- ✅ ClickHouse
## Getting Started
@@ -94,15 +96,44 @@ npm install
VITE_OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> npm run build
```
### Running the Docker Container
### Run the Docker Container
```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 -p 8080:80 chartdb
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 ghcr.io/chartdb/chartdb:latest
```
#### 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`.
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
1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)

View File

@@ -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
View 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
View 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
View 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',
},
},
];

View File

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

20759
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

BIN
public/buckle-animated.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

BIN
public/buckle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

0
public/config.js Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -3,6 +3,7 @@ import React, { lazy, Suspense, useCallback, useEffect } from 'react';
import { Spinner } from '../spinner/spinner';
import { useTheme } from '@/hooks/use-theme';
import { useMonaco } from '@monaco-editor/react';
import { useToast } from '@/components/toast/use-toast';
import { Button } from '../button/button';
import { Copy, CopyCheck } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
@@ -11,6 +12,14 @@ import { DarkTheme } from './themes/dark';
import { LightTheme } from './themes/light';
import './config.ts';
export const Editor = lazy(() =>
import('./code-editor').then((module) => ({
default: module.Editor,
}))
);
type EditorType = typeof Editor;
export interface CodeSnippetProps {
className?: string;
code: string;
@@ -18,14 +27,9 @@ export interface CodeSnippetProps {
loading?: boolean;
autoScroll?: 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(
({
className,
@@ -34,10 +38,12 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
language = 'sql',
autoScroll = false,
isComplete = true,
editorProps,
}) => {
const { t } = useTranslation();
const monaco = useMonaco();
const { effectiveTheme } = useTheme();
const { toast } = useToast();
const [isCopied, setIsCopied] = React.useState(false);
const [tooltipOpen, setTooltipOpen] = React.useState(false);
@@ -66,10 +72,32 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
}
}, [code, monaco, autoScroll]);
const copyToClipboard = useCallback(() => {
navigator.clipboard.writeText(code);
setIsCopied(true);
}, [code]);
const copyToClipboard = useCallback(async () => {
if (!navigator?.clipboard) {
toast({
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 (
<div
@@ -120,27 +148,32 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
language={language}
loading={<Spinner />}
theme={effectiveTheme}
{...editorProps}
options={{
minimap: {
enabled: false,
},
readOnly: true,
automaticLayout: true,
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
alwaysConsumeMouseWheel: false,
},
scrollBeyondLastLine: false,
renderValidationDecorations: 'off',
lineDecorationsWidth: 0,
overviewRulerBorder: false,
overviewRulerLanes: 0,
hideCursorInOverviewRuler: true,
contextmenu: false,
...editorProps?.options,
guides: {
indentation: false,
...editorProps?.options?.guides,
},
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
alwaysConsumeMouseWheel: false,
...editorProps?.options?.scrollbar,
},
minimap: {
enabled: false,
...editorProps?.options?.minimap,
},
contextmenu: false,
}}
/>
{!isComplete ? (

View 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
],
},
});
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import type { Diagram } from '@/lib/domain/diagram';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
import type { DatabaseEdition } from '@/lib/domain/database-edition';
import {
databaseEditionToImageMap,
databaseEditionToLabelMap,
@@ -9,39 +9,44 @@ import {
databaseSecondaryLogoMap,
databaseTypeToLabelMap,
} from '@/lib/databases';
import type { DatabaseType } from '@/lib/domain/database-type';
import { cn } from '@/lib/utils';
export interface DiagramIconProps {
diagram: Diagram;
export interface DiagramIconProps
extends React.ComponentPropsWithoutRef<'div'> {
databaseType: DatabaseType;
databaseEdition?: DatabaseEdition;
imgClassName?: string;
}
export const DiagramIcon = React.forwardRef<
React.ElementRef<typeof TooltipTrigger>,
DiagramIconProps
>(({ diagram }, ref) =>
diagram.databaseEdition ? (
>(({ databaseType, databaseEdition, className, imgClassName }, ref) =>
databaseEdition ? (
<Tooltip>
<TooltipTrigger className="mr-1" ref={ref}>
<TooltipTrigger className={cn('mr-1', className)} ref={ref} asChild>
<img
src={databaseEditionToImageMap[diagram.databaseEdition]}
className="h-5 max-w-fit rounded-full"
src={databaseEditionToImageMap[databaseEdition]}
className={cn('h-5 max-w-fit rounded-full', imgClassName)}
alt="database"
/>
</TooltipTrigger>
<TooltipContent>
{databaseEditionToLabelMap[diagram.databaseEdition]}
{databaseEditionToLabelMap[databaseEdition]}
</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger className="mr-2" ref={ref}>
<TooltipTrigger className={cn('mr-2', className)} ref={ref} asChild>
<img
src={databaseSecondaryLogoMap[diagram.databaseType]}
className="h-5 max-w-fit"
src={databaseSecondaryLogoMap[databaseType]}
className={cn('h-5 max-w-fit', imgClassName)}
alt="database"
/>
</TooltipTrigger>
<TooltipContent>
{databaseTypeToLabelMap[diagram.databaseType]}
{databaseTypeToLabelMap[databaseType]}
</TooltipContent>
</Tooltip>
)

View File

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

View File

@@ -1,30 +1,66 @@
import React, { forwardRef } from 'react';
import EmptyStateImage from '@/assets/empty_state.png';
import EmptyStateImageDark from '@/assets/empty_state_dark.png';
import { Label } from '@/components/label/label';
import { cn } from '@/lib/utils';
import { useTheme } from '@/hooks/use-theme';
export interface EmptyStateProps {
title: string;
description: string;
imageClassName?: string;
titleClassName?: string;
descriptionClassName?: string;
}
export const EmptyState = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & EmptyStateProps
>(({ title, description, className }, ref) => (
<div
ref={ref}
className={cn(
'flex flex-1 flex-col items-center justify-center space-y-1',
className
)}
>
<img src={EmptyStateImage} alt="Empty state" className="w-32" />
<Label className="text-base">{title}</Label>
<Label className="text-sm font-normal text-muted-foreground">
{description}
</Label>
</div>
));
>(
(
{
title,
description,
className,
titleClassName,
descriptionClassName,
imageClassName,
},
ref
) => {
const { effectiveTheme } = useTheme();
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';

View File

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

View File

@@ -14,6 +14,7 @@ type ToasterToast = ToastProps & {
layout?: 'row' | 'column';
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const actionTypes = {
ADD_TOAST: 'ADD_TOAST',
UPDATE_TOAST: 'UPDATE_TOAST',

View 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);

View 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>
);
};

View 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(),
});

View 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>
);
};

View File

@@ -84,6 +84,7 @@ export interface ChartDBContext {
options?: { updateHistory: boolean }
) => Promise<void>;
loadDiagram: (diagramId: string) => Promise<Diagram | undefined>;
loadDiagramFromData: (diagram: Diagram) => void;
updateDiagramUpdatedAt: () => Promise<void>;
clearDiagramData: () => Promise<void>;
deleteDiagram: () => Promise<void>;
@@ -246,6 +247,7 @@ export const chartDBContext = createContext<ChartDBContext>({
updateDiagramName: emptyFn,
updateDiagramUpdatedAt: emptyFn,
loadDiagram: emptyFn,
loadDiagramFromData: emptyFn,
clearDiagramData: emptyFn,
deleteDiagram: emptyFn,

View File

@@ -27,6 +27,7 @@ export interface ChartDBProviderProps {
diagram?: Diagram;
readonly?: boolean;
}
export const ChartDBProvider: React.FC<
React.PropsWithChildren<ChartDBProviderProps>
> = ({ children, diagram, readonly }) => {
@@ -310,6 +311,7 @@ export const ChartDBProvider: React.FC<
color: randomColor(),
createdAt: Date.now(),
isView: false,
order: tables.length,
...attributes,
};
await addTable(table);
@@ -1334,15 +1336,9 @@ export const ChartDBProvider: React.FC<
]
);
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
async (diagramId: string) => {
const diagram = await db.getDiagram(diagramId, {
includeRelationships: true,
includeTables: true,
includeDependencies: true,
});
if (diagram) {
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
useCallback(
async (diagram) => {
setDiagramId(diagram.id);
setDiagramName(diagram.name);
setDatabaseType(diagram.databaseType);
@@ -1354,23 +1350,36 @@ export const ChartDBProvider: React.FC<
setDiagramUpdatedAt(diagram.updatedAt);
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;
},
[
db,
setDiagramId,
setDiagramName,
setDatabaseType,
setDatabaseEdition,
setTables,
setRelationships,
setDependencies,
setDiagramCreatedAt,
setDiagramUpdatedAt,
events,
]
[db, loadDiagramFromData]
);
return (
@@ -1391,6 +1400,7 @@ export const ChartDBProvider: React.FC<
updateDiagramId,
updateDiagramName,
loadDiagram,
loadDiagramFromData,
updateDatabaseType,
updateDatabaseEdition,
clearDiagramData,

View File

@@ -1,12 +1,13 @@
import { createContext } from 'react';
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 { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
export interface DialogContext {
// Create diagram dialog
@@ -21,12 +22,10 @@ export interface DialogContext {
openExportSQLDialog: (params: Omit<ExportSQLDialogProps, 'dialog'>) => void;
closeExportSQLDialog: () => void;
// Alert dialog
showAlert: (params: BaseAlertDialogProps) => void;
closeAlert: () => void;
// Create relationship dialog
openCreateRelationshipDialog: () => void;
openCreateRelationshipDialog: (
params?: Omit<CreateRelationshipDialogProps, 'dialog'>
) => void;
closeCreateRelationshipDialog: () => void;
// Import database dialog
@@ -45,6 +44,10 @@ export interface DialogContext {
openStarUsDialog: () => void;
closeStarUsDialog: () => void;
// Buckle dialog
openBuckleDialog: () => void;
closeBuckleDialog: () => void;
// Export image dialog
openExportImageDialog: (
params: Omit<ExportImageDialogProps, 'dialog'>
@@ -62,6 +65,12 @@ export interface DialogContext {
params: Omit<ImportDiagramDialogProps, 'dialog'>
) => void;
closeImportDiagramDialog: () => void;
// Import DBML dialog
openImportDBMLDialog: (
params?: Omit<ImportDBMLDialogProps, 'dialog'>
) => void;
closeImportDBMLDialog: () => void;
}
export const dialogContext = createContext<DialogContext>({
@@ -71,8 +80,6 @@ export const dialogContext = createContext<DialogContext>({
closeOpenDiagramDialog: emptyFn,
openExportSQLDialog: emptyFn,
closeExportSQLDialog: emptyFn,
closeAlert: emptyFn,
showAlert: emptyFn,
closeCreateRelationshipDialog: emptyFn,
openCreateRelationshipDialog: emptyFn,
openImportDatabaseDialog: emptyFn,
@@ -87,4 +94,8 @@ export const dialogContext = createContext<DialogContext>({
closeExportDiagramDialog: emptyFn,
openImportDiagramDialog: emptyFn,
closeImportDiagramDialog: emptyFn,
openBuckleDialog: emptyFn,
closeBuckleDialog: emptyFn,
openImportDBMLDialog: emptyFn,
closeImportDBMLDialog: emptyFn,
});

View File

@@ -6,8 +6,7 @@ import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-di
import type { ExportSQLDialogProps } 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 type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog';
import { BaseAlertDialog } from '@/dialogs/base-alert-dialog/base-alert-dialog';
import type { CreateRelationshipDialogProps } 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 { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
@@ -19,6 +18,9 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
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> = ({
children,
@@ -28,7 +30,19 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
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 [openBuckleDialog, setOpenBuckleDialog] = useState(false);
// Export image dialog
const [openExportImageDialog, setOpenExportImageDialog] = useState(false);
@@ -88,7 +102,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
[setOpenTableSchemaDialog]
);
// Export image dialog
// Export diagram dialog
const [openExportDiagramDialog, setOpenExportDiagramDialog] =
useState(false);
@@ -96,21 +110,10 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
useState(false);
// Alert dialog
const [showAlert, setShowAlert] = useState(false);
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
title: '',
});
const showAlertHandler: DialogContext['showAlert'] = useCallback(
(params) => {
setAlertParams(params);
setShowAlert(true);
},
[setShowAlert, setAlertParams]
);
const closeAlertHandler = useCallback(() => {
setShowAlert(false);
}, [setShowAlert]);
// Import DBML dialog
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
const [importDBMLDialogParams, setImportDBMLDialogParams] =
useState<Omit<ImportDBMLDialogProps, 'dialog'>>();
return (
<dialogContext.Provider
@@ -121,10 +124,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
openExportSQLDialog: openExportSQLDialogHandler,
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
showAlert: showAlertHandler,
closeAlert: closeAlertHandler,
openCreateRelationshipDialog: () =>
setOpenCreateRelationshipDialog(true),
openCreateRelationshipDialog:
openCreateRelationshipDialogHandler,
closeCreateRelationshipDialog: () =>
setOpenCreateRelationshipDialog(false),
openImportDatabaseDialog: openImportDatabaseDialogHandler,
@@ -134,6 +135,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeTableSchemaDialog: () => setOpenTableSchemaDialog(false),
openStarUsDialog: () => setOpenStarUsDialog(true),
closeStarUsDialog: () => setOpenStarUsDialog(false),
closeBuckleDialog: () => setOpenBuckleDialog(false),
openBuckleDialog: () => setOpenBuckleDialog(true),
closeExportImageDialog: () => setOpenExportImageDialog(false),
openExportImageDialog: openExportImageDialogHandler,
openExportDiagramDialog: () => setOpenExportDiagramDialog(true),
@@ -142,6 +145,11 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
closeImportDiagramDialog: () =>
setOpenImportDiagramDialog(false),
openImportDBMLDialog: (params) => {
setImportDBMLDialogParams(params);
setOpenImportDBMLDialog(true);
},
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
}}
>
{children}
@@ -151,9 +159,9 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
dialog={{ open: openExportSQLDialog }}
{...exportSQLDialogParams}
/>
<BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} />
<CreateRelationshipDialog
dialog={{ open: openCreateRelationshipDialog }}
{...createRelationshipDialogParams}
/>
<ImportDatabaseDialog
dialog={{ open: openImportDatabaseDialog }}
@@ -170,6 +178,11 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
/>
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
<BuckleDialog dialog={{ open: openBuckleDialog }} />
<ImportDBMLDialog
dialog={{ open: openImportDBMLDialog }}
{...importDBMLDialogParams}
/>
</dialogContext.Provider>
);
};

View File

@@ -166,6 +166,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({
},
quality: 1,
pixelRatio: scale,
skipFonts: true,
});
downloadImage(dataUrl, type);

View File

@@ -8,6 +8,8 @@ import {
import { useHistory } from '@/hooks/use-history';
import { useDialog } from '@/hooks/use-dialog';
import { useChartDB } from '@/hooks/use-chartdb';
import { useLayout } from '@/hooks/use-layout';
import { useReactFlow } from '@xyflow/react';
export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -15,6 +17,9 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
const { redo, undo } = useHistory();
const { openOpenDiagramDialog } = useDialog();
const { updateDiagramUpdatedAt } = useChartDB();
const { toggleSidePanel } = useLayout();
const { fitView } = useReactFlow();
useHotkeys(
keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
redo,
@@ -49,6 +54,29 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
},
[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 (
<keyboardShortcutsContext.Provider value={{}}>

View File

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

View File

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

View File

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

View File

@@ -30,8 +30,17 @@ export interface LocalConfigContext {
starUsDialogLastOpen: number;
setStarUsDialogLastOpen: (lastOpen: number) => void;
buckleWaitlistOpened: boolean;
setBuckleWaitlistOpened: (githubRepoOpened: boolean) => void;
buckleDialogLastOpen: number;
setBuckleDialogLastOpen: (lastOpen: number) => void;
showDependenciesOnCanvas: boolean;
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
showMiniMapOnCanvas: boolean;
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
}
export const LocalConfigContext = createContext<LocalConfigContext>({
@@ -56,6 +65,15 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
starUsDialogLastOpen: 0,
setStarUsDialogLastOpen: emptyFn,
buckleWaitlistOpened: false,
setBuckleWaitlistOpened: emptyFn,
buckleDialogLastOpen: 0,
setBuckleDialogLastOpen: emptyFn,
showDependenciesOnCanvas: false,
setShowDependenciesOnCanvas: emptyFn,
showMiniMapOnCanvas: false,
setShowMiniMapOnCanvas: emptyFn,
});

View File

@@ -10,7 +10,10 @@ const showCardinalityKey = 'show_cardinality';
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
const githubRepoOpenedKey = 'github_repo_opened';
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 showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -48,12 +51,28 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
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] =
React.useState<boolean>(
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
'true'
);
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
React.useState<boolean>(
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
);
useEffect(() => {
localStorage.setItem(
starUsDialogLastOpenKey,
@@ -65,6 +84,20 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
}, [githubRepoOpened]);
useEffect(() => {
localStorage.setItem(
buckleDialogLastOpenKey,
buckleDialogLastOpen.toString()
);
}, [buckleDialogLastOpen]);
useEffect(() => {
localStorage.setItem(
buckleWaitlistOpenedKey,
buckleWaitlistOpened.toString()
);
}, [buckleWaitlistOpened]);
useEffect(() => {
localStorage.setItem(
hideMultiSchemaNotificationKey,
@@ -95,6 +128,13 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
);
}, [showDependenciesOnCanvas]);
useEffect(() => {
localStorage.setItem(
showMiniMapOnCanvasKey,
showMiniMapOnCanvas.toString()
);
}, [showMiniMapOnCanvas]);
return (
<LocalConfigContext.Provider
value={{
@@ -114,6 +154,12 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
setStarUsDialogLastOpen,
showDependenciesOnCanvas,
setShowDependenciesOnCanvas,
setBuckleDialogLastOpen,
buckleDialogLastOpen,
buckleWaitlistOpened,
setBuckleWaitlistOpened,
showMiniMapOnCanvas,
setShowMiniMapOnCanvas,
}}
>
{children}

View File

@@ -122,6 +122,32 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
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 () => {
const config = await getConfig();
@@ -270,6 +296,23 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
attributes: Partial<Diagram>;
}) => {
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 (
@@ -345,15 +388,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
.equals(diagramId)
.toArray();
// Sort tables first alphabetically, then views alphabetically
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;
});
return tables;
};
const addRelationship: StorageContext['addRelationship'] = async ({

View File

@@ -10,7 +10,7 @@ import {
AlertDialogTitle,
} from '@/components/alert-dialog/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 {
title: string;
@@ -33,7 +33,7 @@ export const BaseAlertDialog: React.FC<BaseAlertDialogProps> = ({
content,
onClose,
}) => {
const { closeAlert } = useDialog();
const { closeAlert } = useAlert();
const closeAlertHandler = useCallback(() => {
onClose?.();

View 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>
);
};

View File

@@ -5,6 +5,7 @@ import {
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
@@ -84,6 +85,10 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const [showCheckJsonButton, setShowCheckJsonButton] = useState(false);
const [isCheckingJson, setIsCheckingJson] = useState(false);
const [showSSMSInfoDialog, setShowSSMSInfoDialog] = useState(false);
const helpButtonRef = React.useRef<HTMLButtonElement>(null);
useEffect(() => {
const loadScripts = async () => {
const { importMetadataScripts } = await import(
@@ -126,6 +131,16 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const inputValue = e.target.value;
setScriptResult(inputValue);
// Automatically open SSMS info when input length is exactly 65535
if (inputValue.length === 65535) {
setShowSSMSInfoDialog(true);
}
// Show instructions when input contains "WITH fk_info as"
if (inputValue.toLowerCase().includes('with fk_info as')) {
helpButtonRef.current?.click();
}
},
[setScriptResult]
);
@@ -139,6 +154,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
setScriptResult(fixedJson);
setErrorMessage('');
} else {
setScriptResult(fixedJson);
setErrorMessage(errorScriptOutputMessage);
}
@@ -157,188 +173,204 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const renderContent = useCallback(() => {
return (
<div className="flex w-full flex-1 flex-col gap-6">
{databaseTypeToEditionMap[databaseType].length > 0 ? (
<div className="flex flex-col gap-1 md:flex-row">
<p className="text-sm leading-6 text-muted-foreground">
{t(
'new_diagram_dialog.import_database.database_edition'
)}
</p>
<ToggleGroup
type="single"
className="ml-1 flex-wrap gap-2"
value={
!databaseEdition ? 'regular' : databaseEdition
}
onValueChange={(value) => {
setDatabaseEdition(
value === 'regular'
? undefined
: (value as DatabaseEdition)
);
}}
>
<ToggleGroupItem
value="regular"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
<DialogInternalContent>
<div className="flex w-full flex-1 flex-col gap-6">
{databaseTypeToEditionMap[databaseType].length > 0 ? (
<div className="flex flex-col gap-1 md:flex-row">
<p className="text-sm leading-6 text-muted-foreground">
{t(
'new_diagram_dialog.import_database.database_edition'
)}
</p>
<ToggleGroup
type="single"
className="ml-1 flex-wrap gap-2"
value={
!databaseEdition
? 'regular'
: databaseEdition
}
onValueChange={(value) => {
setDatabaseEdition(
value === 'regular'
? undefined
: (value as DatabaseEdition)
);
}}
>
<Avatar className="size-4 rounded-none">
<AvatarImage
src={
databaseSecondaryLogoMap[
databaseType
]
}
alt="Regular"
/>
<AvatarFallback>Regular</AvatarFallback>
</Avatar>
Regular
</ToggleGroupItem>
{databaseTypeToEditionMap[databaseType].map(
(edition) => (
<ToggleGroupItem
value={edition}
key={edition}
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
>
<Avatar className="size-4">
<AvatarImage
src={
databaseEditionToImageMap[
edition
]
}
alt={
databaseEditionToLabelMap[
edition
]
}
/>
<AvatarFallback>
{
databaseEditionToLabelMap[
edition
]
}
</AvatarFallback>
</Avatar>
{databaseEditionToLabelMap[edition]}
</ToggleGroupItem>
)
)}
</ToggleGroup>
</div>
) : null}
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
<div>
1. {t('new_diagram_dialog.import_database.step_1')}
</div>
{databaseType === DatabaseType.SQL_SERVER && (
<SSMSInfo />
)}
</div>
{databaseTypeToClientsMap[databaseType].length > 0 ? (
<Tabs
value={
!databaseClient ? 'dbclient' : databaseClient
}
onValueChange={(value) => {
setDatabaseClient(
value === 'dbclient'
? undefined
: (value as DatabaseClient)
);
}}
>
<div className="flex flex-1">
<TabsList className="h-8 justify-start rounded-none rounded-t-sm ">
<TabsTrigger
value="dbclient"
className="h-6 w-20"
>
DB Client
</TabsTrigger>
{databaseClients?.map((client) => (
<TabsTrigger
key={client}
value={client}
className="h-6 !w-20"
<ToggleGroupItem
value="regular"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
>
<Avatar className="size-4 rounded-none">
<AvatarImage
src={
databaseSecondaryLogoMap[
databaseType
]
}
alt="Regular"
/>
<AvatarFallback>Regular</AvatarFallback>
</Avatar>
Regular
</ToggleGroupItem>
{databaseTypeToEditionMap[databaseType].map(
(edition) => (
<ToggleGroupItem
value={edition}
key={edition}
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none"
>
{databaseClientToLabelMap[client]}
</TabsTrigger>
)) ?? []}
</TabsList>
<Avatar className="size-4">
<AvatarImage
src={
databaseEditionToImageMap[
edition
]
}
alt={
databaseEditionToLabelMap[
edition
]
}
/>
<AvatarFallback>
{
databaseEditionToLabelMap[
edition
]
}
</AvatarFallback>
</Avatar>
{databaseEditionToLabelMap[edition]}
</ToggleGroupItem>
)
)}
</ToggleGroup>
</div>
) : null}
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
<div>
1.{' '}
{t('new_diagram_dialog.import_database.step_1')}
</div>
{databaseType === DatabaseType.SQL_SERVER && (
<SSMSInfo
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
className="h-40 w-full"
className="h-40 w-full flex-auto"
loading={!importMetadataScripts}
code={
importMetadataScripts?.[databaseType]?.({
databaseEdition,
databaseClient,
}) ?? ''
}
language={databaseClient ? 'shell' : 'sql'}
language="sql"
/>
</Tabs>
) : (
<CodeSnippet
className="h-40 w-full flex-auto"
loading={!importMetadataScripts}
code={
importMetadataScripts?.[databaseType]?.({
databaseEdition,
}) ?? ''
}
language="sql"
/>
)}
</div>
<div className="flex h-48 flex-col gap-1">
<p className="text-sm text-muted-foreground">
2. {t('new_diagram_dialog.import_database.step_2')}
</p>
<Textarea
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
placeholder={t(
'new_diagram_dialog.import_database.script_results_placeholder'
)}
value={scriptResult}
onChange={handleInputChange}
/>
{showCheckJsonButton || errorMessage ? (
<div className="mt-2 flex items-center gap-2">
{showCheckJsonButton ? (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleCheckJson}
disabled={isCheckingJson}
>
{isCheckingJson ? (
<Spinner size="small" />
) : (
t(
'new_diagram_dialog.import_database.check_script_result'
)
)}
</Button>
) : (
<p className="text-sm text-red-700">
{errorMessage}
</p>
</div>
<div className="flex h-48 flex-col gap-1">
<p className="text-sm text-muted-foreground">
2. {t('new_diagram_dialog.import_database.step_2')}
</p>
<Textarea
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
placeholder={t(
'new_diagram_dialog.import_database.script_results_placeholder'
)}
</div>
) : null}
value={scriptResult}
onChange={handleInputChange}
/>
{showCheckJsonButton || errorMessage ? (
<div className="mt-2 flex items-center gap-2">
{showCheckJsonButton ? (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleCheckJson}
disabled={isCheckingJson}
>
{isCheckingJson ? (
<Spinner size="small" />
) : (
t(
'new_diagram_dialog.import_database.check_script_result'
)
)}
</Button>
) : (
<p className="text-sm text-red-700">
{errorMessage}
</p>
)}
</div>
) : null}
</div>
</div>
</div>
</DialogInternalContent>
);
}, [
databaseEdition,
@@ -354,6 +386,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
showCheckJsonButton,
isCheckingJson,
handleCheckJson,
showSSMSInfoDialog,
setShowSSMSInfoDialog,
]);
const renderFooter = useCallback(() => {
@@ -371,7 +405,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
)}
{isDesktop ? (
<ZoomableImage src="/load-new-db-instructions.gif">
<Button type="button" variant="link">
<Button
type="button"
variant="link"
ref={helpButtonRef}
>
{t(
'new_diagram_dialog.import_database.instructions_link'
)}
@@ -423,7 +461,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
{!isDesktop ? (
<ZoomableImage src="/load-new-db-instructions.gif">
<Button type="button" variant="link">
<Button
type="button"
variant="link"
ref={helpButtonRef}
>
{t(
'new_diagram_dialog.import_database.instructions_link'
)}

View File

@@ -4,32 +4,55 @@ import {
HoverCardTrigger,
} from '@/components/hover-card/hover-card';
import { Label } from '@/components/label/label';
import { Info } from 'lucide-react';
import React from 'react';
import { Info, X } from 'lucide-react';
import React, { useCallback, useEffect, useMemo } from 'react';
import SSMSInstructions from '@/assets/ssms-instructions.png';
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
import { useTranslation } from 'react-i18next';
export interface SSMSInfoProps {}
export interface SSMSInfoProps {
open?: boolean;
setOpen?: (open: boolean) => void;
}
export const SSMSInfo = React.forwardRef<
React.ElementRef<typeof HoverCardTrigger>,
SSMSInfoProps
>((props, ref) => {
>(({ open: controlledOpen, setOpen: setControlledOpen }, ref) => {
const [open, setOpen] = React.useState(false);
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 (
<HoverCard
open={open}
open={isOpen}
onOpenChange={(isOpen) => {
if (controlledOpen) {
return;
}
setOpen(isOpen);
}}
>
<HoverCardTrigger ref={ref} {...props} asChild>
<HoverCardTrigger ref={ref} asChild>
<div
className="flex flex-row items-center gap-1 text-pink-600"
onClick={() => {
setOpen(!open);
setOpen?.(!open);
}}
>
<Info size={14} />
@@ -41,13 +64,21 @@ export const SSMSInfo = React.forwardRef<
</div>
</HoverCardTrigger>
<HoverCardContent className="w-80">
<div className="flex">
<div className="space-y-1">
<div className="flex flex-col">
<div className="flex items-start justify-between">
<h4 className="text-sm font-semibold">
{t(
'new_diagram_dialog.import_database.ssms_instructions.title'
)}
</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">
<span className="font-semibold">1. </span>
{t(

View File

@@ -28,7 +28,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
const [databaseType, setDatabaseType] = useState<DatabaseType>(
DatabaseType.GENERIC
);
const { closeCreateDiagramDialog } = useDialog();
const { closeCreateDiagramDialog, openImportDBMLDialog } = useDialog();
const { updateConfig } = useConfig();
const [scriptResult, setScriptResult] = useState('');
const [databaseEdition, setDatabaseEdition] = useState<
@@ -104,6 +104,10 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
await updateConfig({ defaultDiagramId: diagram.id });
closeCreateDiagramDialog();
navigate(`/diagrams/${diagram.id}`);
setTimeout(
() => openImportDBMLDialog({ withCreateEmptyDiagram: true }),
700
);
}, [
databaseType,
addDiagram,
@@ -112,6 +116,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
navigate,
updateConfig,
diagramNumber,
openImportDBMLDialog,
]);
return (
@@ -128,7 +133,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
}}
>
<DialogContent
className="flex w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]"
className="flex max-h-screen w-[90vw] max-w-[90vw] flex-col overflow-y-auto md:overflow-visible lg:max-w-[60vw] xl:lg:max-w-lg xl:min-w-[45vw]"
showClose={hasExistingDiagram}
>
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (

View File

@@ -8,10 +8,13 @@ export interface ExampleOptionProps {}
export const ExampleOption: React.FC<ExampleOptionProps> = () => {
const { t } = useTranslation();
return (
<Link href="/examples" className="text-primary hover:text-primary">
<div className="flex size-20 cursor-pointer flex-col items-center rounded-md border py-3 text-center md:size-32">
<div className="flex flex-1 items-center">
<LayoutGrid size={34} />
<Link
href="/examples"
className="col-span-3 text-primary hover:text-primary"
>
<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 className="flex flex-col-reverse">
<div className="hidden text-sm text-primary md:flex">

View File

@@ -1,22 +1,61 @@
import React from 'react';
import React, { useMemo, useState } from 'react';
import { ToggleGroup } from '@/components/toggle/toggle-group';
import { DatabaseType } from '@/lib/domain/database-type';
import { DatabaseOption } from './database-option';
import { ExampleOption } from './example-option';
import { Button } from '@/components/button/button';
import { ChevronDown, ChevronUp } from 'lucide-react';
export interface SelectDatabaseContentProps {
databaseType: DatabaseType;
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
onContinue: () => void;
}
const ROW_SIZE = 3;
const ROWS = 2;
const TOTAL_SLOTS = ROW_SIZE * ROWS;
const SUPPORTED_DB_TYPES: DatabaseType[] = [
DatabaseType.MYSQL,
DatabaseType.POSTGRESQL,
DatabaseType.MARIADB,
DatabaseType.SQLITE,
DatabaseType.SQL_SERVER,
DatabaseType.COCKROACHDB,
DatabaseType.CLICKHOUSE,
];
export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
databaseType,
setDatabaseType,
onContinue,
}) => {
const [currentRow, setCurrentRow] = useState(0);
const currentDatabasesTypes = useMemo(
() =>
SUPPORTED_DB_TYPES.slice(
currentRow * ROW_SIZE,
currentRow * ROW_SIZE + TOTAL_SLOTS
),
[currentRow]
);
const hasNextRow = useMemo(
() => (currentRow + 1) * ROW_SIZE < SUPPORTED_DB_TYPES.length,
[currentRow]
);
const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]);
const toggleRow = () => {
if (currentRow === 0 && hasNextRow) {
setCurrentRow(currentRow + 1);
} else if (currentRow > 0) {
setCurrentRow(currentRow - 1);
}
};
return (
<div className="flex flex-1 items-center justify-center">
<div className="flex flex-1 flex-col items-center justify-center gap-4">
<ToggleGroup
value={databaseType}
onValueChange={(value: DatabaseType) => {
@@ -30,12 +69,41 @@ export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
type="single"
className="grid grid-flow-row grid-cols-3 gap-6"
>
<DatabaseOption type={DatabaseType.MYSQL} />
<DatabaseOption type={DatabaseType.POSTGRESQL} />
<DatabaseOption type={DatabaseType.MARIADB} />
<DatabaseOption type={DatabaseType.SQLITE} />
<DatabaseOption type={DatabaseType.SQL_SERVER} />
<ExampleOption />
{Array.from({ length: TOTAL_SLOTS }).map((_, index) =>
currentDatabasesTypes?.[index] ? (
<DatabaseOption
key={currentDatabasesTypes[index]}
type={currentDatabasesTypes[index]}
/>
) : null
)}
<div className="col-span-3 flex flex-1 flex-col gap-1">
{hasNextRow || hasPreviousRow ? (
<Button
variant="ghost"
onClick={toggleRow}
className="col-span-3 h-8"
>
{currentRow === 0 ? (
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 py-3 text-center md:h-12">
<ChevronDown className="mr-2 size-3.5" />
<span className="text-xs">
More Databases
</span>
</div>
) : (
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 py-3 text-center md:h-12">
<ChevronUp className="mr-2 size-3.5" />
<span className="text-xs">
Primary Databases
</span>
</div>
)}
</Button>
) : null}
<ExampleOption />
</div>
</ToggleGroup>
</div>
);

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