Compare commits
	
		
			107 Commits
		
	
	
		
			v1.2.0
			...
			jf/wrong_i
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 595e3db0b3 | ||
|  | ab89bad6d5 | ||
|  | deb218423f | ||
|  | 48342471ac | ||
|  | 47bb87a88f | ||
|  | a96c2e1078 | ||
|  | 26d95eed25 | ||
|  | be65328f24 | ||
|  | 85fd14fa02 | ||
|  | 9c485b3b01 | ||
|  | e993f1549c | ||
|  | 0db67ea42a | ||
|  | b9e621bd68 | ||
|  | 93d59f8887 | ||
|  | 190e4f4ffa | ||
|  | dc404c9d7e | ||
|  | dd4324d64f | ||
|  | 1878083056 | ||
|  | 7b6271962a | ||
|  | 2edc8dfde8 | ||
|  | 004d530880 | ||
|  | fd2cc9fcfc | ||
|  | 4c93326bb6 | ||
|  | ef3d7a8b67 | ||
|  | 3b3be086b1 | ||
|  | b424518212 | ||
|  | 99a8201398 | ||
|  | eb9b41e4f6 | ||
|  | fef6d3f499 | ||
|  | 14f11c27a7 | ||
|  | 2118bce0f0 | ||
|  | 88be6c1fd4 | ||
|  | 0dcc9b9568 | ||
|  | ff3269ec05 | ||
|  | 659dc2e3e7 | ||
|  | c36cd33180 | ||
|  | 58231c9139 | ||
|  | 1643e7bdeb | ||
|  | 42d4cbac8c | ||
|  | 7452ca6965 | ||
|  | 27aede7794 | ||
|  | e9e2736cb2 | ||
|  | 74c1730425 | ||
|  | 94bed7fcce | ||
|  | 8abf2a7bfc | ||
|  | ee659eaa03 | ||
|  | 7c5db0848e | ||
|  | 4b43f720e9 | ||
|  | 766b5164b8 | ||
|  | 7868ca9f42 | ||
|  | 0411742864 | ||
|  | 9831ac5a10 | ||
|  | 91c6fb9249 | ||
|  | c155013668 | ||
|  | 1b0f293c87 | ||
|  | df2dc03aa0 | ||
|  | 205d431c89 | ||
|  | 0abe18cdf9 | ||
|  | a151f56b5d | ||
|  | 2b6b733261 | ||
|  | b56b04925c | ||
|  | 635fb53c9f | ||
|  | d6659795bc | ||
|  | 348f80568e | ||
|  | 5f9c74a9ad | ||
|  | 5409288388 | ||
|  | 2309306ef5 | ||
|  | 3574cecc7c | ||
|  | 29b8edc051 | ||
|  | 5fc10a7e64 | ||
|  | 807cd22e0c | ||
|  | 03772f6b4f | ||
|  | 885eb719de | ||
|  | 94656ec7a5 | ||
|  | a0e966b64f | ||
|  | a8fe491c1b | ||
|  | ddeef3b134 | ||
|  | d45677e92d | ||
|  | 9c7d03c285 | ||
|  | be1b109f23 | ||
|  | 05eaf85a3d | ||
|  | 53f443d452 | ||
|  | 134c62f931 | ||
|  | 4bb4766e1a | ||
|  | 24db32369a | ||
|  | e77ee60a5b | ||
|  | 6c65c2e9cc | ||
|  | 70f545f78b | ||
|  | fb702c87ce | ||
|  | eaa067814f | ||
|  | 667685ed0f | ||
|  | 2940431efa | ||
|  | 94ec43b608 | ||
|  | a2efed803f | ||
|  | 8749591be0 | ||
|  | c5e0ea6fa4 | ||
|  | ab07da0b03 | ||
|  | 8397bef392 | ||
|  | 7c3c62860e | ||
|  | 76ba4ce4c5 | ||
|  | 0c0fad719f | ||
|  | b75c6fe4e7 | ||
|  | d9fcbeec72 | ||
|  | 5d79721b6d | ||
|  | 4be3592cf4 | ||
|  | b4cdcbbbd7 | ||
|  | e9c7f4be06 | 
| @@ -1,29 +0,0 @@ | ||||
| module.exports = { | ||||
|     root: true, | ||||
|     env: { browser: true, es2020: true }, | ||||
|     extends: [ | ||||
|         'eslint:recommended', | ||||
|         'plugin:react/recommended', | ||||
|         'plugin:@typescript-eslint/recommended', | ||||
|         'plugin:react-hooks/recommended', | ||||
|         'plugin:css-modules/recommended', | ||||
|         'plugin:tailwindcss/recommended', | ||||
|         'plugin:prettier/recommended', | ||||
|         // 'plugin:jsx-a11y/recommended', | ||||
|     ], | ||||
|     ignorePatterns: ['dist', '.eslintrc.cjs'], | ||||
|     parser: '@typescript-eslint/parser', | ||||
|     plugins: ['react-refresh', 'css-modules', 'tailwindcss', 'jsx-a11y'], | ||||
|     rules: { | ||||
|         '@typescript-eslint/consistent-type-imports': 'error', | ||||
|         'react-refresh/only-export-components': [ | ||||
|             'warn', | ||||
|             { allowConstantExport: true }, | ||||
|         ], | ||||
|         'react/no-unescaped-entities': 'off', | ||||
|         'react/prop-types': 'off', | ||||
|     }, | ||||
|     settings: { | ||||
|         react: { version: 'detect' }, | ||||
|     }, | ||||
| }; | ||||
							
								
								
									
										151
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -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) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						| @@ -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"] | ||||
							
								
								
									
										39
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -16,6 +16,7 @@ | ||||
| <h3 align="center"> | ||||
|   <a href="https://discord.gg/QeFwyWSKwC">Community</a>  • | ||||
|   <a href="https://www.chartdb.io?ref=github_readme">Website</a>  • | ||||
|   <a href="https://chartdb.io/templates?ref=github_readme">Examples</a>  • | ||||
|   <a href="https://app.chartdb.io?ref=github_readme">Demo</a> | ||||
| </h3> | ||||
|  | ||||
| @@ -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) | ||||
|   | ||||
							
								
								
									
										15
									
								
								default.conf
									
									
									
									
									
								
							
							
						
						| @@ -1,15 +0,0 @@ | ||||
| server { | ||||
|     listen       80; | ||||
|     listen  [::]:80; | ||||
|  | ||||
|     location / { | ||||
|         root   /usr/share/nginx/html; | ||||
|         index  index.html index.htm; | ||||
| 	    try_files  $uri $uri/ /index.html; | ||||
|     } | ||||
|  | ||||
|     error_page   500 502 503 504  /50x.html; | ||||
|     location = /50x.html { | ||||
|         root   /usr/share/nginx/html; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								default.conf.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,25 @@ | ||||
| server { | ||||
|     listen       80; | ||||
|     listen  [::]:80; | ||||
|  | ||||
|     location / { | ||||
|         root   /usr/share/nginx/html; | ||||
|         index  index.html index.htm; | ||||
| 	    try_files  $uri $uri/ /index.html; | ||||
|     } | ||||
|  | ||||
|     location /config.js { | ||||
|         default_type application/javascript; | ||||
|         return 200 "window.env = {  | ||||
|             OPENAI_API_KEY: \"$OPENAI_API_KEY\", | ||||
|             OPENAI_API_ENDPOINT: \"$OPENAI_API_ENDPOINT\", | ||||
|             LLM_MODEL_NAME: \"$LLM_MODEL_NAME\", | ||||
|             HIDE_BUCKLE_DOT_DEV: \"$HIDE_BUCKLE_DOT_DEV\" | ||||
|         };"; | ||||
|     } | ||||
|  | ||||
|     error_page   500 502 503 504  /50x.html; | ||||
|     location = /50x.html { | ||||
|         root   /usr/share/nginx/html; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								entrypoint.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| # Replace placeholders in nginx.conf | ||||
| envsubst '${OPENAI_API_KEY} ${OPENAI_API_ENDPOINT} ${LLM_MODEL_NAME} ${HIDE_BUCKLE_DOT_DEV}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf | ||||
|  | ||||
| # Start Nginx | ||||
| nginx -g "daemon off;" | ||||
							
								
								
									
										77
									
								
								eslint.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,77 @@ | ||||
| import { fixupConfigRules, fixupPluginRules } from '@eslint/compat'; | ||||
| import reactRefresh from 'eslint-plugin-react-refresh'; | ||||
| import cssModules from 'eslint-plugin-css-modules'; | ||||
| import tailwindcss from 'eslint-plugin-tailwindcss'; | ||||
| import jsxA11Y from 'eslint-plugin-jsx-a11y'; | ||||
| import globals from 'globals'; | ||||
| import tsParser from '@typescript-eslint/parser'; | ||||
| import path from 'node:path'; | ||||
| import { fileURLToPath } from 'node:url'; | ||||
| import js from '@eslint/js'; | ||||
| import { FlatCompat } from '@eslint/eslintrc'; | ||||
|  | ||||
| const __filename = fileURLToPath(import.meta.url); | ||||
| const __dirname = path.dirname(__filename); | ||||
| const compat = new FlatCompat({ | ||||
|     baseDirectory: __dirname, | ||||
|     recommendedConfig: js.configs.recommended, | ||||
|     allConfig: js.configs.all, | ||||
| }); | ||||
|  | ||||
| export default [ | ||||
|     { | ||||
|         ignores: ['**/dist', '**/.eslintrc.cjs', '**/tailwind.config.js'], | ||||
|         // files: ['**/*.ts', '**/*.tsx'], | ||||
|     }, | ||||
|     ...fixupConfigRules( | ||||
|         compat.extends( | ||||
|             'eslint:recommended', | ||||
|             'plugin:react/recommended', | ||||
|             'plugin:@typescript-eslint/recommended', | ||||
|             'plugin:react-hooks/recommended', | ||||
|             'plugin:css-modules/recommended', | ||||
|             'plugin:tailwindcss/recommended', | ||||
|             'plugin:prettier/recommended' | ||||
|         ) | ||||
|     ), | ||||
|     { | ||||
|         plugins: { | ||||
|             'react-refresh': reactRefresh, | ||||
|             'css-modules': fixupPluginRules(cssModules), | ||||
|             tailwindcss: fixupPluginRules(tailwindcss), | ||||
|             'jsx-a11y': jsxA11Y, | ||||
|         }, | ||||
|  | ||||
|         languageOptions: { | ||||
|             globals: { | ||||
|                 ...globals.browser, | ||||
|             }, | ||||
|  | ||||
|             parser: tsParser, | ||||
|             // parserOptions: { | ||||
|             //     project: './tsconfig.json', | ||||
|             // }, | ||||
|         }, | ||||
|  | ||||
|         settings: { | ||||
|             react: { | ||||
|                 version: 'detect', | ||||
|             }, | ||||
|         }, | ||||
|  | ||||
|         rules: { | ||||
|             '@typescript-eslint/consistent-type-imports': 'error', | ||||
|  | ||||
|             'react-refresh/only-export-components': [ | ||||
|                 'warn', | ||||
|                 { | ||||
|                     allowConstantExport: true, | ||||
|                 }, | ||||
|             ], | ||||
|  | ||||
|             'react/no-unescaped-entities': 'off', | ||||
|             'react/prop-types': 'off', | ||||
|             '@typescript-eslint/no-empty-object-type': 'off', | ||||
|         }, | ||||
|     }, | ||||
| ]; | ||||
| @@ -12,6 +12,7 @@ | ||||
|             href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap" | ||||
|             rel="stylesheet" | ||||
|         /> | ||||
|         <script src="/config.js"></script> | ||||
|         <script | ||||
|             src="https://cdn.usefathom.com/script.js" | ||||
|             data-site="PRHIVBNN" | ||||
|   | ||||
							
								
								
									
										20759
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										195
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| After Width: | Height: | Size: 404 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/buckle.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 28 KiB | 
							
								
								
									
										0
									
								
								public/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								src/assets/clickhouse_logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/clickhouse_logo_2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/clickhouse_logo_dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/cockroachdb_logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/cockroachdb_logo_2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 270 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/cockroachdb_logo_dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 6.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/empty_state_dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/bouncer-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 323 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/bouncer-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 348 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/cabot-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 420 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/cabot-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 498 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/cachet-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 447 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/cachet-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 486 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/canvas-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 346 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/canvas-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 379 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/deployer-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 424 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/deployer-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 497 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/devise-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 207 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/devise-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 231 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/django-axes-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 250 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/django-axes-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 264 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/doorkeeper-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 288 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/doorkeeper-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 319 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/feedbin-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 498 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/feedbin-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 583 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/flarum-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 412 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/flarum-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 499 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/flipper-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 189 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/flipper-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 207 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/freescout-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 441 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/freescout-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 502 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/hacker-news-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 427 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/hacker-news-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 472 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/laravel-activitylog-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 198 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/laravel-activitylog-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 217 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/octobox-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 352 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/octobox-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 382 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/orchid-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 303 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/orchid-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 340 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/pay-rails-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 352 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/pay-rails-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 371 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/pixelfed-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 593 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/pixelfed-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 687 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/polr-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 246 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/polr-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 278 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/reversion-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 229 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/reversion-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 266 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/screeenly-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 251 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/screeenly-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 266 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/staytus-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 424 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/staytus-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 471 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/taggit-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 169 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/taggit-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 184 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/talk-db-dark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 229 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/templates/talk-db.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 253 KiB | 
| @@ -3,6 +3,7 @@ import React, { lazy, Suspense, useCallback, useEffect } from 'react'; | ||||
| import { Spinner } from '../spinner/spinner'; | ||||
| import { 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 ? ( | ||||
|   | ||||
							
								
								
									
										54
									
								
								src/components/code-snippet/languages/dbml-language.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,54 @@ | ||||
| import type { Monaco } from '@monaco-editor/react'; | ||||
| import { dataTypes } from '@/lib/data/data-types/data-types'; | ||||
|  | ||||
| export const setupDBMLLanguage = (monaco: Monaco) => { | ||||
|     monaco.languages.register({ id: 'dbml' }); | ||||
|  | ||||
|     // Define themes for DBML | ||||
|     monaco.editor.defineTheme('dbml-dark', { | ||||
|         base: 'vs-dark', | ||||
|         inherit: true, | ||||
|         rules: [ | ||||
|             { token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords | ||||
|             { token: 'string', foreground: 'CE9178' }, // Strings | ||||
|             { token: 'annotation', foreground: '9CDCFE' }, // [annotations] | ||||
|             { token: 'delimiter', foreground: 'D4D4D4' }, // Braces {} | ||||
|             { token: 'operator', foreground: 'D4D4D4' }, // Operators | ||||
|             { token: 'datatype', foreground: '4EC9B0' }, // Data types | ||||
|         ], | ||||
|         colors: {}, | ||||
|     }); | ||||
|  | ||||
|     monaco.editor.defineTheme('dbml-light', { | ||||
|         base: 'vs', | ||||
|         inherit: true, | ||||
|         rules: [ | ||||
|             { token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords | ||||
|             { token: 'string', foreground: 'A31515' }, // Strings | ||||
|             { token: 'annotation', foreground: '001080' }, // [annotations] | ||||
|             { token: 'delimiter', foreground: '000000' }, // Braces {} | ||||
|             { token: 'operator', foreground: '000000' }, // Operators | ||||
|             { token: 'type', foreground: '267F99' }, // Data types | ||||
|         ], | ||||
|         colors: {}, | ||||
|     }); | ||||
|  | ||||
|     const dataTypesNames = dataTypes.map((dt) => dt.name); | ||||
|     const datatypePattern = dataTypesNames.join('|'); | ||||
|  | ||||
|     monaco.languages.setMonarchTokensProvider('dbml', { | ||||
|         keywords: ['Table', 'Ref', 'Indexes'], | ||||
|         datatypes: dataTypesNames, | ||||
|         tokenizer: { | ||||
|             root: [ | ||||
|                 [/\b(Table|Ref|Indexes)\b/, 'keyword'], | ||||
|                 [/\[.*?\]/, 'annotation'], | ||||
|                 [/".*?"/, 'string'], | ||||
|                 [/'.*?'/, 'string'], | ||||
|                 [/[{}]/, 'delimiter'], | ||||
|                 [/[<>]/, 'operator'], | ||||
|                 [new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching | ||||
|             ], | ||||
|         }, | ||||
|     }); | ||||
| }; | ||||
| @@ -1,6 +1,6 @@ | ||||
| import React from 'react'; | ||||
| import 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> | ||||
|     ) | ||||
|   | ||||
| @@ -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, | ||||
| }; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
							
								
								
									
										15
									
								
								src/context/alert-context/alert-context.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| import { createContext, useContext } from 'react'; | ||||
| import { emptyFn } from '@/lib/utils'; | ||||
| import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog'; | ||||
|  | ||||
| export interface AlertContext { | ||||
|     showAlert: (params: BaseAlertDialogProps) => void; | ||||
|     closeAlert: () => void; | ||||
| } | ||||
|  | ||||
| export const alertContext = createContext<AlertContext>({ | ||||
|     closeAlert: emptyFn, | ||||
|     showAlert: emptyFn, | ||||
| }); | ||||
|  | ||||
| export const useAlert = () => useContext(alertContext); | ||||
							
								
								
									
										36
									
								
								src/context/alert-context/alert-provider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | ||||
| import React, { useCallback, useState } from 'react'; | ||||
| import type { AlertContext } from './alert-context'; | ||||
| import { alertContext } from './alert-context'; | ||||
| import type { BaseAlertDialogProps } from '@/dialogs/base-alert-dialog/base-alert-dialog'; | ||||
| import { BaseAlertDialog } from '@/dialogs/base-alert-dialog/base-alert-dialog'; | ||||
|  | ||||
| export const AlertProvider: React.FC<React.PropsWithChildren> = ({ | ||||
|     children, | ||||
| }) => { | ||||
|     const [showAlert, setShowAlert] = useState(false); | ||||
|     const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({ | ||||
|         title: '', | ||||
|     }); | ||||
|     const showAlertHandler: AlertContext['showAlert'] = useCallback( | ||||
|         (params) => { | ||||
|             setAlertParams(params); | ||||
|             setShowAlert(true); | ||||
|         }, | ||||
|         [setShowAlert, setAlertParams] | ||||
|     ); | ||||
|     const closeAlertHandler = useCallback(() => { | ||||
|         setShowAlert(false); | ||||
|     }, [setShowAlert]); | ||||
|  | ||||
|     return ( | ||||
|         <alertContext.Provider | ||||
|             value={{ | ||||
|                 showAlert: showAlertHandler, | ||||
|                 closeAlert: closeAlertHandler, | ||||
|             }} | ||||
|         > | ||||
|             {children} | ||||
|             <BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} /> | ||||
|         </alertContext.Provider> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										22
									
								
								src/context/canvas-context/canvas-context.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | ||||
| import { createContext } from 'react'; | ||||
| import { emptyFn } from '@/lib/utils'; | ||||
| import type { Graph } from '@/lib/graph'; | ||||
| import { createGraph } from '@/lib/graph'; | ||||
|  | ||||
| export interface CanvasContext { | ||||
|     reorderTables: (options?: { updateHistory?: boolean }) => void; | ||||
|     fitView: (options?: { | ||||
|         duration?: number; | ||||
|         padding?: number; | ||||
|         maxZoom?: number; | ||||
|     }) => void; | ||||
|     setOverlapGraph: (graph: Graph<string>) => void; | ||||
|     overlapGraph: Graph<string>; | ||||
| } | ||||
|  | ||||
| export const canvasContext = createContext<CanvasContext>({ | ||||
|     reorderTables: emptyFn, | ||||
|     fitView: emptyFn, | ||||
|     setOverlapGraph: emptyFn, | ||||
|     overlapGraph: createGraph(), | ||||
| }); | ||||
							
								
								
									
										85
									
								
								src/context/canvas-context/canvas-provider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,85 @@ | ||||
| import React, { type ReactNode, useCallback, useState } from 'react'; | ||||
| import { canvasContext } from './canvas-context'; | ||||
| import { useChartDB } from '@/hooks/use-chartdb'; | ||||
| import { | ||||
|     adjustTablePositions, | ||||
|     shouldShowTablesBySchemaFilter, | ||||
| } from '@/lib/domain/db-table'; | ||||
| import { useReactFlow } from '@xyflow/react'; | ||||
| import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils'; | ||||
| import type { Graph } from '@/lib/graph'; | ||||
| import { createGraph } from '@/lib/graph'; | ||||
|  | ||||
| interface CanvasProviderProps { | ||||
|     children: ReactNode; | ||||
| } | ||||
|  | ||||
| export const CanvasProvider = ({ children }: CanvasProviderProps) => { | ||||
|     const { tables, relationships, updateTablesState, filteredSchemas } = | ||||
|         useChartDB(); | ||||
|     const { fitView } = useReactFlow(); | ||||
|     const [overlapGraph, setOverlapGraph] = | ||||
|         useState<Graph<string>>(createGraph()); | ||||
|  | ||||
|     const reorderTables = useCallback( | ||||
|         ( | ||||
|             options: { updateHistory?: boolean } = { | ||||
|                 updateHistory: true, | ||||
|             } | ||||
|         ) => { | ||||
|             const newTables = adjustTablePositions({ | ||||
|                 relationships, | ||||
|                 tables: tables.filter((table) => | ||||
|                     shouldShowTablesBySchemaFilter(table, filteredSchemas) | ||||
|                 ), | ||||
|                 mode: 'all', // Use 'all' mode for manual reordering | ||||
|             }); | ||||
|  | ||||
|             const updatedOverlapGraph = findOverlappingTables({ | ||||
|                 tables: newTables, | ||||
|             }); | ||||
|  | ||||
|             updateTablesState( | ||||
|                 (currentTables) => | ||||
|                     currentTables.map((table) => { | ||||
|                         const newTable = newTables.find( | ||||
|                             (t) => t.id === table.id | ||||
|                         ); | ||||
|                         return { | ||||
|                             id: table.id, | ||||
|                             x: newTable?.x ?? table.x, | ||||
|                             y: newTable?.y ?? table.y, | ||||
|                         }; | ||||
|                     }), | ||||
|                 { | ||||
|                     updateHistory: options.updateHistory ?? true, | ||||
|                     forceOverride: false, | ||||
|                 } | ||||
|             ); | ||||
|  | ||||
|             setOverlapGraph(updatedOverlapGraph); | ||||
|  | ||||
|             setTimeout(() => { | ||||
|                 fitView({ | ||||
|                     duration: 500, | ||||
|                     padding: 0.2, | ||||
|                     maxZoom: 0.8, | ||||
|                 }); | ||||
|             }, 500); | ||||
|         }, | ||||
|         [filteredSchemas, relationships, tables, updateTablesState, fitView] | ||||
|     ); | ||||
|  | ||||
|     return ( | ||||
|         <canvasContext.Provider | ||||
|             value={{ | ||||
|                 reorderTables, | ||||
|                 fitView, | ||||
|                 setOverlapGraph, | ||||
|                 overlapGraph, | ||||
|             }} | ||||
|         > | ||||
|             {children} | ||||
|         </canvasContext.Provider> | ||||
|     ); | ||||
| }; | ||||
| @@ -84,6 +84,7 @@ export interface ChartDBContext { | ||||
|         options?: { updateHistory: boolean } | ||||
|     ) => 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, | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
| }); | ||||
|   | ||||
| @@ -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> | ||||
|     ); | ||||
| }; | ||||
|   | ||||
| @@ -166,6 +166,7 @@ export const ExportImageProvider: React.FC<React.PropsWithChildren> = ({ | ||||
|                         }, | ||||
|                         quality: 1, | ||||
|                         pixelRatio: scale, | ||||
|                         skipFonts: true, | ||||
|                     }); | ||||
|  | ||||
|                     downloadImage(dataUrl, type); | ||||
|   | ||||
| @@ -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={{}}> | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -22,6 +22,7 @@ export interface LayoutContext { | ||||
|     isSidePanelShowed: boolean; | ||||
|     hideSidePanel: () => void; | ||||
|     showSidePanel: () => void; | ||||
|     toggleSidePanel: () => void; | ||||
|  | ||||
|     isSelectSchemaOpen: boolean; | ||||
|     openSelectSchema: () => void; | ||||
| @@ -47,6 +48,7 @@ export const layoutContext = createContext<LayoutContext>({ | ||||
|     isSidePanelShowed: false, | ||||
|     hideSidePanel: emptyFn, | ||||
|     showSidePanel: emptyFn, | ||||
|     toggleSidePanel: emptyFn, | ||||
|  | ||||
|     isSelectSchemaOpen: false, | ||||
|     openSelectSchema: emptyFn, | ||||
|   | ||||
| @@ -36,6 +36,10 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({ | ||||
|     const showSidePanel: LayoutContext['showSidePanel'] = () => | ||||
|         setIsSidePanelShowed(true); | ||||
|  | ||||
|     const toggleSidePanel: LayoutContext['toggleSidePanel'] = () => { | ||||
|         setIsSidePanelShowed((prevIsSidePanelShowed) => !prevIsSidePanelShowed); | ||||
|     }; | ||||
|  | ||||
|     const openTableFromSidebar: LayoutContext['openTableFromSidebar'] = ( | ||||
|         tableId | ||||
|     ) => { | ||||
| @@ -77,6 +81,7 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({ | ||||
|                 isSidePanelShowed, | ||||
|                 hideSidePanel, | ||||
|                 showSidePanel, | ||||
|                 toggleSidePanel, | ||||
|                 isSelectSchemaOpen, | ||||
|                 openSelectSchema, | ||||
|                 closeSelectSchema, | ||||
|   | ||||
| @@ -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, | ||||
| }); | ||||
|   | ||||
| @@ -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} | ||||
|   | ||||
| @@ -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 ({ | ||||
|   | ||||
| @@ -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?.(); | ||||
|   | ||||
							
								
								
									
										80
									
								
								src/dialogs/buckle-dialog/buckle-dialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,80 @@ | ||||
| import React, { useCallback, useEffect } from 'react'; | ||||
| import { useDialog } from '@/hooks/use-dialog'; | ||||
| import { | ||||
|     Dialog, | ||||
|     DialogClose, | ||||
|     DialogContent, | ||||
|     DialogDescription, | ||||
|     DialogFooter, | ||||
|     DialogHeader, | ||||
|     DialogTitle, | ||||
| } from '@/components/dialog/dialog'; | ||||
| import { Button } from '@/components/button/button'; | ||||
| import type { BaseDialogProps } from '../common/base-dialog-props'; | ||||
| import { useLocalConfig } from '@/hooks/use-local-config'; | ||||
| import { useTheme } from '@/hooks/use-theme'; | ||||
|  | ||||
| export interface BuckleDialogProps extends BaseDialogProps {} | ||||
|  | ||||
| export const BuckleDialog: React.FC<BuckleDialogProps> = ({ dialog }) => { | ||||
|     const { setBuckleWaitlistOpened } = useLocalConfig(); | ||||
|     const { effectiveTheme } = useTheme(); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (!dialog.open) return; | ||||
|     }, [dialog.open]); | ||||
|     const { closeBuckleDialog } = useDialog(); | ||||
|  | ||||
|     const handleConfirm = useCallback(() => { | ||||
|         setBuckleWaitlistOpened(true); | ||||
|         window.open('https://waitlist.buckle.dev', '_blank'); | ||||
|     }, [setBuckleWaitlistOpened]); | ||||
|  | ||||
|     return ( | ||||
|         <Dialog | ||||
|             {...dialog} | ||||
|             onOpenChange={(open) => { | ||||
|                 if (!open) { | ||||
|                     closeBuckleDialog(); | ||||
|                 } | ||||
|             }} | ||||
|         > | ||||
|             <DialogContent | ||||
|                 className="flex flex-col" | ||||
|                 showClose={false} | ||||
|                 onInteractOutside={(e) => { | ||||
|                     e.preventDefault(); | ||||
|                 }} | ||||
|             > | ||||
|                 <DialogHeader> | ||||
|                     <DialogTitle className="hidden" /> | ||||
|                     <DialogDescription className="hidden" /> | ||||
|                 </DialogHeader> | ||||
|                 <div className="flex w-full flex-col items-center"> | ||||
|                     <img | ||||
|                         src={ | ||||
|                             effectiveTheme === 'light' | ||||
|                                 ? '/buckle-animated.gif' | ||||
|                                 : '/buckle.png' | ||||
|                         } | ||||
|                         className="h-16" | ||||
|                     /> | ||||
|                     <div className="mt-6 text-center text-base"> | ||||
|                         We've been working on something big -{' '} | ||||
|                         <span className="font-semibold">Ready to explore?</span> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <DialogFooter className="flex gap-1 md:justify-between"> | ||||
|                     <DialogClose asChild> | ||||
|                         <Button variant="secondary">Not now</Button> | ||||
|                     </DialogClose> | ||||
|                     <DialogClose asChild> | ||||
|                         <Button onClick={handleConfirm}> | ||||
|                             Try ChartDB v2.0! | ||||
|                         </Button> | ||||
|                     </DialogClose> | ||||
|                 </DialogFooter> | ||||
|             </DialogContent> | ||||
|         </Dialog> | ||||
|     ); | ||||
| }; | ||||
| @@ -5,6 +5,7 @@ import { | ||||
|     DialogDescription, | ||||
|     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' | ||||
|                                 )} | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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 ? ( | ||||
|   | ||||
| @@ -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"> | ||||
|   | ||||
| @@ -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> | ||||
|     ); | ||||
|   | ||||