Compare commits
	
		
			84 Commits
		
	
	
		
			v1.3.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 | 
@@ -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' },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										125
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,5 +1,130 @@
 | 
			
		||||
# 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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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"]
 | 
			
		||||
							
								
								
									
										38
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -68,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
 | 
			
		||||
@@ -95,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"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5327
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										23
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -1,18 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "chartdb",
 | 
			
		||||
    "private": true,
 | 
			
		||||
    "version": "1.3.0",
 | 
			
		||||
    "version": "1.8.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": "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",
 | 
			
		||||
@@ -28,10 +29,10 @@
 | 
			
		||||
        "@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-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.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",
 | 
			
		||||
@@ -60,7 +61,7 @@
 | 
			
		||||
        "react-i18next": "^15.0.1",
 | 
			
		||||
        "react-resizable-panels": "^2.0.22",
 | 
			
		||||
        "react-responsive": "^10.0.0",
 | 
			
		||||
        "react-router-dom": "^6.26.0",
 | 
			
		||||
        "react-router-dom": "^7.1.1",
 | 
			
		||||
        "react-use": "^17.5.1",
 | 
			
		||||
        "tailwind-merge": "^2.4.0",
 | 
			
		||||
        "tailwindcss-animate": "^1.0.7",
 | 
			
		||||
@@ -69,22 +70,26 @@
 | 
			
		||||
        "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": "^7.15.0",
 | 
			
		||||
        "@typescript-eslint/parser": "^7.15.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": "^8.57.0",
 | 
			
		||||
        "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": "^4.6.2",
 | 
			
		||||
        "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",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											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/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/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/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);
 | 
			
		||||
        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);
 | 
			
		||||
        }, [code]);
 | 
			
		||||
            } 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>
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,10 @@ const DialogInternalContent = React.forwardRef<
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
    <ScrollArea
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        className={cn('flex max-h-screen flex-col overflow-y-auto', className)}
 | 
			
		||||
        className={cn(
 | 
			
		||||
            'flex flex-1 max-h-screen flex-col overflow-y-auto',
 | 
			
		||||
            className
 | 
			
		||||
        )}
 | 
			
		||||
        {...props}
 | 
			
		||||
    />
 | 
			
		||||
));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,36 @@
 | 
			
		||||
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) => (
 | 
			
		||||
>(
 | 
			
		||||
    (
 | 
			
		||||
        {
 | 
			
		||||
            title,
 | 
			
		||||
            description,
 | 
			
		||||
            className,
 | 
			
		||||
            titleClassName,
 | 
			
		||||
            descriptionClassName,
 | 
			
		||||
            imageClassName,
 | 
			
		||||
        },
 | 
			
		||||
        ref
 | 
			
		||||
    ) => {
 | 
			
		||||
        const { effectiveTheme } = useTheme();
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                ref={ref}
 | 
			
		||||
                className={cn(
 | 
			
		||||
@@ -19,12 +38,29 @@ export const EmptyState = forwardRef<
 | 
			
		||||
                    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">
 | 
			
		||||
                <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';
 | 
			
		||||
 
 | 
			
		||||
@@ -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,12 +1350,8 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
                setDiagramUpdatedAt(diagram.updatedAt);
 | 
			
		||||
 | 
			
		||||
                events.emit({ action: 'load_diagram', data: { diagram } });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return diagram;
 | 
			
		||||
            },
 | 
			
		||||
            [
 | 
			
		||||
            db,
 | 
			
		||||
                setDiagramId,
 | 
			
		||||
                setDiagramName,
 | 
			
		||||
                setDatabaseType,
 | 
			
		||||
@@ -1373,6 +1365,23 @@ 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) {
 | 
			
		||||
                loadDiagramFromData(diagram);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return diagram;
 | 
			
		||||
        },
 | 
			
		||||
        [db, loadDiagramFromData]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <chartDBContext.Provider
 | 
			
		||||
            value={{
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ 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,
 | 
			
		||||
@@ -17,6 +18,7 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
 | 
			
		||||
    const { openOpenDiagramDialog } = useDialog();
 | 
			
		||||
    const { updateDiagramUpdatedAt } = useChartDB();
 | 
			
		||||
    const { toggleSidePanel } = useLayout();
 | 
			
		||||
    const { fitView } = useReactFlow();
 | 
			
		||||
 | 
			
		||||
    useHotkeys(
 | 
			
		||||
        keyboardShortcutsForOS[KeyboardShortcutAction.REDO].keyCombination,
 | 
			
		||||
@@ -61,6 +63,20 @@ export const KeyboardShortcutsProvider: React.FC<React.PropsWithChildren> = ({
 | 
			
		||||
        },
 | 
			
		||||
        [toggleSidePanel]
 | 
			
		||||
    );
 | 
			
		||||
    useHotkeys(
 | 
			
		||||
        keyboardShortcutsForOS[KeyboardShortcutAction.SHOW_ALL].keyCombination,
 | 
			
		||||
        () => {
 | 
			
		||||
            fitView({
 | 
			
		||||
                duration: 500,
 | 
			
		||||
                padding: 0.1,
 | 
			
		||||
                maxZoom: 0.8,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            preventDefault: true,
 | 
			
		||||
        },
 | 
			
		||||
        [fitView]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <keyboardShortcutsContext.Provider value={{}}>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ export enum KeyboardShortcutAction {
 | 
			
		||||
    OPEN_DIAGRAM = 'open_diagram',
 | 
			
		||||
    SAVE_DIAGRAM = 'save_diagram',
 | 
			
		||||
    TOGGLE_SIDE_PANEL = 'toggle_side_panel',
 | 
			
		||||
    SHOW_ALL = 'show_all',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface KeyboardShortcut {
 | 
			
		||||
@@ -55,6 +56,13 @@ export const keyboardShortcuts: Record<
 | 
			
		||||
        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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -85,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(
 | 
			
		||||
@@ -127,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]
 | 
			
		||||
    );
 | 
			
		||||
@@ -245,7 +259,10 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
 | 
			
		||||
                                {t('new_diagram_dialog.import_database.step_1')}
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {databaseType === DatabaseType.SQL_SERVER && (
 | 
			
		||||
                                <SSMSInfo />
 | 
			
		||||
                                <SSMSInfo
 | 
			
		||||
                                    open={showSSMSInfoDialog}
 | 
			
		||||
                                    setOpen={setShowSSMSInfoDialog}
 | 
			
		||||
                                />
 | 
			
		||||
                            )}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {databaseTypeToClientsMap[databaseType].length > 0 ? (
 | 
			
		||||
@@ -369,6 +386,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
 | 
			
		||||
        showCheckJsonButton,
 | 
			
		||||
        isCheckingJson,
 | 
			
		||||
        handleCheckJson,
 | 
			
		||||
        showSSMSInfoDialog,
 | 
			
		||||
        setShowSSMSInfoDialog,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const renderFooter = useCallback(() => {
 | 
			
		||||
@@ -386,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'
 | 
			
		||||
                                )}
 | 
			
		||||
@@ -438,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 (
 | 
			
		||||
 
 | 
			
		||||
@@ -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} />
 | 
			
		||||
                {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>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -22,13 +22,17 @@ import { areFieldTypesCompatible } from '@/lib/data/data-types/data-types';
 | 
			
		||||
const ErrorMessageRelationshipFieldsNotSameType =
 | 
			
		||||
    'Relationships can only be created between fields of the same type';
 | 
			
		||||
 | 
			
		||||
export interface CreateRelationshipDialogProps extends BaseDialogProps {}
 | 
			
		||||
export interface CreateRelationshipDialogProps extends BaseDialogProps {
 | 
			
		||||
    sourceTableId?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CreateRelationshipDialog: React.FC<
 | 
			
		||||
    CreateRelationshipDialogProps
 | 
			
		||||
> = ({ dialog }) => {
 | 
			
		||||
> = ({ dialog, sourceTableId: preSelectedSourceTableId }) => {
 | 
			
		||||
    const { closeCreateRelationshipDialog } = useDialog();
 | 
			
		||||
    const [primaryTableId, setPrimaryTableId] = useState<string | undefined>();
 | 
			
		||||
    const [primaryTableId, setPrimaryTableId] = useState<string | undefined>(
 | 
			
		||||
        preSelectedSourceTableId
 | 
			
		||||
    );
 | 
			
		||||
    const [primaryFieldId, setPrimaryFieldId] = useState<string | undefined>();
 | 
			
		||||
    const [referencedTableId, setReferencedTableId] = useState<
 | 
			
		||||
        string | undefined
 | 
			
		||||
@@ -43,6 +47,9 @@ export const CreateRelationshipDialog: React.FC<
 | 
			
		||||
    const [canCreateRelationship, setCanCreateRelationship] = useState(false);
 | 
			
		||||
    const { fitView, setEdges } = useReactFlow();
 | 
			
		||||
    const { databaseType } = useChartDB();
 | 
			
		||||
    const [primaryFieldSelectOpen, setPrimaryFieldSelectOpen] = useState(false);
 | 
			
		||||
    const [referencedTableSelectOpen, setReferencedTableSelectOpen] =
 | 
			
		||||
        useState(false);
 | 
			
		||||
 | 
			
		||||
    const tableOptions = useMemo(() => {
 | 
			
		||||
        return tables.map(
 | 
			
		||||
@@ -89,8 +96,23 @@ export const CreateRelationshipDialog: React.FC<
 | 
			
		||||
        setReferencedTableId(undefined);
 | 
			
		||||
        setReferencedFieldId(undefined);
 | 
			
		||||
        setErrorMessage('');
 | 
			
		||||
        setPrimaryFieldSelectOpen(false);
 | 
			
		||||
        setReferencedTableSelectOpen(false);
 | 
			
		||||
    }, [dialog.open]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (preSelectedSourceTableId) {
 | 
			
		||||
            const table = getTable(preSelectedSourceTableId);
 | 
			
		||||
            if (table) {
 | 
			
		||||
                setPrimaryTableId(preSelectedSourceTableId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                setPrimaryFieldSelectOpen(true);
 | 
			
		||||
            }, 100);
 | 
			
		||||
        }
 | 
			
		||||
    }, [preSelectedSourceTableId, getTable]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setCanCreateRelationship(false);
 | 
			
		||||
        setErrorMessage('');
 | 
			
		||||
@@ -223,8 +245,14 @@ export const CreateRelationshipDialog: React.FC<
 | 
			
		||||
                                    )}
 | 
			
		||||
                                    value={primaryTableId}
 | 
			
		||||
                                    onChange={(value) => {
 | 
			
		||||
                                        setPrimaryTableId(value as string);
 | 
			
		||||
                                        const newTableId = value as string;
 | 
			
		||||
                                        setPrimaryTableId(newTableId);
 | 
			
		||||
                                        if (
 | 
			
		||||
                                            newTableId !==
 | 
			
		||||
                                            preSelectedSourceTableId
 | 
			
		||||
                                        ) {
 | 
			
		||||
                                            setPrimaryFieldId(undefined);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }}
 | 
			
		||||
                                    emptyPlaceholder={t(
 | 
			
		||||
                                        'create_relationship_dialog.no_tables_found'
 | 
			
		||||
@@ -253,6 +281,8 @@ export const CreateRelationshipDialog: React.FC<
 | 
			
		||||
                                            'create_relationship_dialog.primary_field_placeholder'
 | 
			
		||||
                                        )}
 | 
			
		||||
                                        value={primaryFieldId}
 | 
			
		||||
                                        open={primaryFieldSelectOpen}
 | 
			
		||||
                                        onOpenChange={setPrimaryFieldSelectOpen}
 | 
			
		||||
                                        onChange={(value) =>
 | 
			
		||||
                                            setPrimaryFieldId(value as string)
 | 
			
		||||
                                        }
 | 
			
		||||
@@ -283,6 +313,8 @@ export const CreateRelationshipDialog: React.FC<
 | 
			
		||||
                                        'create_relationship_dialog.referenced_table_placeholder'
 | 
			
		||||
                                    )}
 | 
			
		||||
                                    value={referencedTableId}
 | 
			
		||||
                                    open={referencedTableSelectOpen}
 | 
			
		||||
                                    onOpenChange={setReferencedTableSelectOpen}
 | 
			
		||||
                                    onChange={(value) => {
 | 
			
		||||
                                        setReferencedTableId(value as string);
 | 
			
		||||
                                        setReferencedFieldId(undefined);
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,12 @@ import {
 | 
			
		||||
} from '@/lib/data/export-metadata/export-sql-script';
 | 
			
		||||
import { databaseTypeToLabelMap } from '@/lib/databases';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
 | 
			
		||||
import { Annoyed, Sparkles } from 'lucide-react';
 | 
			
		||||
import React, { useCallback, useEffect, useRef } from 'react';
 | 
			
		||||
import { Trans, useTranslation } from 'react-i18next';
 | 
			
		||||
import type { BaseDialogProps } from '../common/base-dialog-props';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
 | 
			
		||||
export interface ExportSQLDialogProps extends BaseDialogProps {
 | 
			
		||||
    targetDatabaseType: DatabaseType;
 | 
			
		||||
@@ -34,7 +36,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
 | 
			
		||||
    targetDatabaseType,
 | 
			
		||||
}) => {
 | 
			
		||||
    const { closeExportSQLDialog } = useDialog();
 | 
			
		||||
    const { currentDiagram } = useChartDB();
 | 
			
		||||
    const { currentDiagram, filteredSchemas } = useChartDB();
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const [script, setScript] = React.useState<string>();
 | 
			
		||||
    const [error, setError] = React.useState<boolean>(false);
 | 
			
		||||
@@ -43,17 +45,58 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
 | 
			
		||||
    const abortControllerRef = useRef<AbortController | null>(null);
 | 
			
		||||
 | 
			
		||||
    const exportSQLScript = useCallback(async () => {
 | 
			
		||||
        const filteredDiagram: Diagram = {
 | 
			
		||||
            ...currentDiagram,
 | 
			
		||||
            tables: currentDiagram.tables?.filter((table) =>
 | 
			
		||||
                shouldShowTablesBySchemaFilter(table, filteredSchemas)
 | 
			
		||||
            ),
 | 
			
		||||
            relationships: currentDiagram.relationships?.filter((rel) => {
 | 
			
		||||
                const sourceTable = currentDiagram.tables?.find(
 | 
			
		||||
                    (t) => t.id === rel.sourceTableId
 | 
			
		||||
                );
 | 
			
		||||
                const targetTable = currentDiagram.tables?.find(
 | 
			
		||||
                    (t) => t.id === rel.targetTableId
 | 
			
		||||
                );
 | 
			
		||||
                return (
 | 
			
		||||
                    sourceTable &&
 | 
			
		||||
                    targetTable &&
 | 
			
		||||
                    shouldShowTablesBySchemaFilter(
 | 
			
		||||
                        sourceTable,
 | 
			
		||||
                        filteredSchemas
 | 
			
		||||
                    ) &&
 | 
			
		||||
                    shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
 | 
			
		||||
                );
 | 
			
		||||
            }),
 | 
			
		||||
            dependencies: currentDiagram.dependencies?.filter((dep) => {
 | 
			
		||||
                const table = currentDiagram.tables?.find(
 | 
			
		||||
                    (t) => t.id === dep.tableId
 | 
			
		||||
                );
 | 
			
		||||
                const dependentTable = currentDiagram.tables?.find(
 | 
			
		||||
                    (t) => t.id === dep.dependentTableId
 | 
			
		||||
                );
 | 
			
		||||
                return (
 | 
			
		||||
                    table &&
 | 
			
		||||
                    dependentTable &&
 | 
			
		||||
                    shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
 | 
			
		||||
                    shouldShowTablesBySchemaFilter(
 | 
			
		||||
                        dependentTable,
 | 
			
		||||
                        filteredSchemas
 | 
			
		||||
                    )
 | 
			
		||||
                );
 | 
			
		||||
            }),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (targetDatabaseType === DatabaseType.GENERIC) {
 | 
			
		||||
            return Promise.resolve(exportBaseSQL(currentDiagram));
 | 
			
		||||
            return Promise.resolve(exportBaseSQL(filteredDiagram));
 | 
			
		||||
        } else {
 | 
			
		||||
            return exportSQL(currentDiagram, targetDatabaseType, {
 | 
			
		||||
            return exportSQL(filteredDiagram, targetDatabaseType, {
 | 
			
		||||
                stream: true,
 | 
			
		||||
                onResultStream: (text) =>
 | 
			
		||||
                    setScript((prev) => (prev ? prev + text : text)),
 | 
			
		||||
                signal: abortControllerRef.current?.signal,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }, [targetDatabaseType, currentDiagram]);
 | 
			
		||||
    }, [targetDatabaseType, currentDiagram, filteredSchemas]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!dialog.open) {
 | 
			
		||||
@@ -70,7 +113,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
 | 
			
		||||
                const script = await exportSQLScript();
 | 
			
		||||
                setScript(script);
 | 
			
		||||
                setIsScriptLoading(false);
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
            } catch {
 | 
			
		||||
                setError(true);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { Dialog, DialogContent } from '@/components/dialog/dialog';
 | 
			
		||||
import { useDialog } from '@/hooks/use-dialog';
 | 
			
		||||
import type { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import React, { useCallback, useEffect, useState } from 'react';
 | 
			
		||||
import { ImportDatabase } from '../common/import-database/import-database';
 | 
			
		||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
 | 
			
		||||
@@ -12,6 +12,7 @@ import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
 | 
			
		||||
import { Trans, useTranslation } from 'react-i18next';
 | 
			
		||||
import { useReactFlow } from '@xyflow/react';
 | 
			
		||||
import type { BaseDialogProps } from '../common/base-dialog-props';
 | 
			
		||||
import { useAlert } from '@/context/alert-context/alert-context';
 | 
			
		||||
 | 
			
		||||
export interface ImportDatabaseDialogProps extends BaseDialogProps {
 | 
			
		||||
    databaseType: DatabaseType;
 | 
			
		||||
@@ -21,7 +22,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
 | 
			
		||||
    dialog,
 | 
			
		||||
    databaseType,
 | 
			
		||||
}) => {
 | 
			
		||||
    const { closeImportDatabaseDialog, showAlert } = useDialog();
 | 
			
		||||
    const { closeImportDatabaseDialog } = useDialog();
 | 
			
		||||
    const { showAlert } = useAlert();
 | 
			
		||||
    const {
 | 
			
		||||
        tables,
 | 
			
		||||
        relationships,
 | 
			
		||||
@@ -30,6 +32,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
 | 
			
		||||
        addTables,
 | 
			
		||||
        addRelationships,
 | 
			
		||||
        diagramName,
 | 
			
		||||
        databaseType: currentDatabaseType,
 | 
			
		||||
        updateDatabaseType,
 | 
			
		||||
    } = useChartDB();
 | 
			
		||||
    const [scriptResult, setScriptResult] = useState('');
 | 
			
		||||
    const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
 | 
			
		||||
@@ -282,6 +286,10 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
 | 
			
		||||
            }),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        if (currentDatabaseType === DatabaseType.GENERIC) {
 | 
			
		||||
            await updateDatabaseType(databaseType);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setNodes((nodes) =>
 | 
			
		||||
            nodes.map((node) => ({
 | 
			
		||||
                ...node,
 | 
			
		||||
@@ -297,6 +305,8 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
 | 
			
		||||
        closeImportDatabaseDialog();
 | 
			
		||||
    }, [
 | 
			
		||||
        databaseEdition,
 | 
			
		||||
        currentDatabaseType,
 | 
			
		||||
        updateDatabaseType,
 | 
			
		||||
        databaseType,
 | 
			
		||||
        scriptResult,
 | 
			
		||||
        tables,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										410
									
								
								src/dialogs/import-dbml-dialog/import-dbml-dialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,410 @@
 | 
			
		||||
import React, {
 | 
			
		||||
    useCallback,
 | 
			
		||||
    useEffect,
 | 
			
		||||
    useState,
 | 
			
		||||
    Suspense,
 | 
			
		||||
    useRef,
 | 
			
		||||
} from 'react';
 | 
			
		||||
import * as monaco from 'monaco-editor';
 | 
			
		||||
import { useDialog } from '@/hooks/use-dialog';
 | 
			
		||||
import {
 | 
			
		||||
    Dialog,
 | 
			
		||||
    DialogClose,
 | 
			
		||||
    DialogContent,
 | 
			
		||||
    DialogDescription,
 | 
			
		||||
    DialogFooter,
 | 
			
		||||
    DialogHeader,
 | 
			
		||||
    DialogInternalContent,
 | 
			
		||||
    DialogTitle,
 | 
			
		||||
} from '@/components/dialog/dialog';
 | 
			
		||||
import { Button } from '@/components/button/button';
 | 
			
		||||
import type { BaseDialogProps } from '../common/base-dialog-props';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { Editor } from '@/components/code-snippet/code-snippet';
 | 
			
		||||
import { useTheme } from '@/hooks/use-theme';
 | 
			
		||||
import { AlertCircle } from 'lucide-react';
 | 
			
		||||
import { importDBMLToDiagram } from '@/lib/dbml-import';
 | 
			
		||||
import { useChartDB } from '@/hooks/use-chartdb';
 | 
			
		||||
import { Parser } from '@dbml/core';
 | 
			
		||||
import { useCanvas } from '@/hooks/use-canvas';
 | 
			
		||||
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
 | 
			
		||||
import { useToast } from '@/components/toast/use-toast';
 | 
			
		||||
import { Spinner } from '@/components/spinner/spinner';
 | 
			
		||||
import { debounce } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
interface DBMLError {
 | 
			
		||||
    message: string;
 | 
			
		||||
    line: number;
 | 
			
		||||
    column: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseDBMLError(error: unknown): DBMLError | null {
 | 
			
		||||
    try {
 | 
			
		||||
        if (typeof error === 'string') {
 | 
			
		||||
            const parsed = JSON.parse(error);
 | 
			
		||||
            if (parsed.diags?.[0]) {
 | 
			
		||||
                const diag = parsed.diags[0];
 | 
			
		||||
                return {
 | 
			
		||||
                    message: diag.message,
 | 
			
		||||
                    line: diag.location.start.line,
 | 
			
		||||
                    column: diag.location.start.column,
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        } else if (error && typeof error === 'object' && 'diags' in error) {
 | 
			
		||||
            const parsed = error as {
 | 
			
		||||
                diags: Array<{
 | 
			
		||||
                    message: string;
 | 
			
		||||
                    location: { start: { line: number; column: number } };
 | 
			
		||||
                }>;
 | 
			
		||||
            };
 | 
			
		||||
            if (parsed.diags?.[0]) {
 | 
			
		||||
                return {
 | 
			
		||||
                    message: parsed.diags[0].message,
 | 
			
		||||
                    line: parsed.diags[0].location.start.line,
 | 
			
		||||
                    column: parsed.diags[0].location.start.column,
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        console.error('Error parsing DBML error:', e);
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ImportDBMLDialogProps extends BaseDialogProps {
 | 
			
		||||
    withCreateEmptyDiagram?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ImportDBMLDialog: React.FC<ImportDBMLDialogProps> = ({
 | 
			
		||||
    dialog,
 | 
			
		||||
    withCreateEmptyDiagram,
 | 
			
		||||
}) => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const initialDBML = `// Use DBML to define your database structure
 | 
			
		||||
// Simple Blog System with Comments Example
 | 
			
		||||
 | 
			
		||||
Table users {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  name varchar
 | 
			
		||||
  email varchar
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table posts {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  title varchar
 | 
			
		||||
  content text
 | 
			
		||||
  user_id integer
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table comments {
 | 
			
		||||
  id integer [primary key]
 | 
			
		||||
  content text
 | 
			
		||||
  post_id integer
 | 
			
		||||
  user_id integer
 | 
			
		||||
  created_at timestamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Relationships
 | 
			
		||||
Ref: posts.user_id > users.id // Each post belongs to one user
 | 
			
		||||
Ref: comments.post_id > posts.id // Each comment belongs to one post
 | 
			
		||||
Ref: comments.user_id > users.id // Each comment is written by one user`;
 | 
			
		||||
 | 
			
		||||
    const [dbmlContent, setDBMLContent] = useState<string>(initialDBML);
 | 
			
		||||
    const { closeImportDBMLDialog } = useDialog();
 | 
			
		||||
    const [errorMessage, setErrorMessage] = useState<string | undefined>();
 | 
			
		||||
    const { effectiveTheme } = useTheme();
 | 
			
		||||
    const { toast } = useToast();
 | 
			
		||||
    const {
 | 
			
		||||
        addTables,
 | 
			
		||||
        addRelationships,
 | 
			
		||||
        tables,
 | 
			
		||||
        relationships,
 | 
			
		||||
        removeTables,
 | 
			
		||||
        removeRelationships,
 | 
			
		||||
    } = useChartDB();
 | 
			
		||||
    const { reorderTables } = useCanvas();
 | 
			
		||||
    const [reorder, setReorder] = useState(false);
 | 
			
		||||
    const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
 | 
			
		||||
    const decorationsCollection =
 | 
			
		||||
        useRef<monaco.editor.IEditorDecorationsCollection>();
 | 
			
		||||
 | 
			
		||||
    const handleEditorDidMount = (
 | 
			
		||||
        editor: monaco.editor.IStandaloneCodeEditor
 | 
			
		||||
    ) => {
 | 
			
		||||
        editorRef.current = editor;
 | 
			
		||||
        decorationsCollection.current = editor.createDecorationsCollection();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (reorder) {
 | 
			
		||||
            reorderTables({
 | 
			
		||||
                updateHistory: false,
 | 
			
		||||
            });
 | 
			
		||||
            setReorder(false);
 | 
			
		||||
        }
 | 
			
		||||
    }, [reorder, reorderTables]);
 | 
			
		||||
 | 
			
		||||
    const highlightErrorLine = useCallback((error: DBMLError) => {
 | 
			
		||||
        if (!editorRef.current) return;
 | 
			
		||||
 | 
			
		||||
        const model = editorRef.current.getModel();
 | 
			
		||||
        if (!model) return;
 | 
			
		||||
 | 
			
		||||
        const decorations = [
 | 
			
		||||
            {
 | 
			
		||||
                range: new monaco.Range(
 | 
			
		||||
                    error.line,
 | 
			
		||||
                    1,
 | 
			
		||||
                    error.line,
 | 
			
		||||
                    model.getLineMaxColumn(error.line)
 | 
			
		||||
                ),
 | 
			
		||||
                options: {
 | 
			
		||||
                    isWholeLine: true,
 | 
			
		||||
                    className: 'dbml-error-line',
 | 
			
		||||
                    glyphMarginClassName: 'dbml-error-glyph',
 | 
			
		||||
                    hoverMessage: { value: error.message },
 | 
			
		||||
                    overviewRuler: {
 | 
			
		||||
                        color: '#ff0000',
 | 
			
		||||
                        position: monaco.editor.OverviewRulerLane.Right,
 | 
			
		||||
                        darkColor: '#ff0000',
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        decorationsCollection.current?.set(decorations);
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const clearDecorations = useCallback(() => {
 | 
			
		||||
        decorationsCollection.current?.clear();
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const validateDBML = useCallback(
 | 
			
		||||
        async (content: string) => {
 | 
			
		||||
            // Clear previous errors
 | 
			
		||||
            setErrorMessage(undefined);
 | 
			
		||||
            clearDecorations();
 | 
			
		||||
 | 
			
		||||
            if (!content.trim()) return;
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                const parser = new Parser();
 | 
			
		||||
                parser.parse(content, 'dbml');
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                const parsedError = parseDBMLError(e);
 | 
			
		||||
                if (parsedError) {
 | 
			
		||||
                    setErrorMessage(
 | 
			
		||||
                        t('import_dbml_dialog.error.description') +
 | 
			
		||||
                            ` (1 error found - in line ${parsedError.line})`
 | 
			
		||||
                    );
 | 
			
		||||
                    highlightErrorLine(parsedError);
 | 
			
		||||
                } else {
 | 
			
		||||
                    setErrorMessage(
 | 
			
		||||
                        e instanceof Error ? e.message : JSON.stringify(e)
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        [clearDecorations, highlightErrorLine, t]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const debouncedValidateRef = useRef<((value: string) => void) | null>(null);
 | 
			
		||||
 | 
			
		||||
    // Set up debounced validation
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        debouncedValidateRef.current = debounce((value: string) => {
 | 
			
		||||
            validateDBML(value);
 | 
			
		||||
        }, 500);
 | 
			
		||||
 | 
			
		||||
        return () => {
 | 
			
		||||
            debouncedValidateRef.current = null;
 | 
			
		||||
        };
 | 
			
		||||
    }, [validateDBML]);
 | 
			
		||||
 | 
			
		||||
    // Trigger validation when content changes
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (debouncedValidateRef.current) {
 | 
			
		||||
            debouncedValidateRef.current(dbmlContent);
 | 
			
		||||
        }
 | 
			
		||||
    }, [dbmlContent]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!dialog.open) {
 | 
			
		||||
            setErrorMessage(undefined);
 | 
			
		||||
            clearDecorations();
 | 
			
		||||
            setDBMLContent(initialDBML);
 | 
			
		||||
        }
 | 
			
		||||
    }, [dialog.open, initialDBML, clearDecorations]);
 | 
			
		||||
 | 
			
		||||
    const handleImport = useCallback(async () => {
 | 
			
		||||
        if (!dbmlContent.trim() || errorMessage) return;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const importedDiagram = await importDBMLToDiagram(dbmlContent);
 | 
			
		||||
            const tableIdsToRemove = tables
 | 
			
		||||
                .filter((table) =>
 | 
			
		||||
                    importedDiagram.tables?.some(
 | 
			
		||||
                        (t) =>
 | 
			
		||||
                            t.name === table.name && t.schema === table.schema
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
                .map((table) => table.id);
 | 
			
		||||
            // Find relationships that need to be removed
 | 
			
		||||
            const relationshipIdsToRemove = relationships
 | 
			
		||||
                .filter((relationship) => {
 | 
			
		||||
                    const sourceTable = tables.find(
 | 
			
		||||
                        (table) => table.id === relationship.sourceTableId
 | 
			
		||||
                    );
 | 
			
		||||
                    const targetTable = tables.find(
 | 
			
		||||
                        (table) => table.id === relationship.targetTableId
 | 
			
		||||
                    );
 | 
			
		||||
                    if (!sourceTable || !targetTable) return true;
 | 
			
		||||
                    const replacementSourceTable = importedDiagram.tables?.find(
 | 
			
		||||
                        (table) =>
 | 
			
		||||
                            table.name === sourceTable.name &&
 | 
			
		||||
                            table.schema === sourceTable.schema
 | 
			
		||||
                    );
 | 
			
		||||
                    const replacementTargetTable = importedDiagram.tables?.find(
 | 
			
		||||
                        (table) =>
 | 
			
		||||
                            table.name === targetTable.name &&
 | 
			
		||||
                            table.schema === targetTable.schema
 | 
			
		||||
                    );
 | 
			
		||||
                    return replacementSourceTable || replacementTargetTable;
 | 
			
		||||
                })
 | 
			
		||||
                .map((relationship) => relationship.id);
 | 
			
		||||
 | 
			
		||||
            // Remove existing items
 | 
			
		||||
            await Promise.all([
 | 
			
		||||
                removeTables(tableIdsToRemove, { updateHistory: false }),
 | 
			
		||||
                removeRelationships(relationshipIdsToRemove, {
 | 
			
		||||
                    updateHistory: false,
 | 
			
		||||
                }),
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            // Add new items
 | 
			
		||||
            await Promise.all([
 | 
			
		||||
                addTables(importedDiagram.tables ?? [], {
 | 
			
		||||
                    updateHistory: false,
 | 
			
		||||
                }),
 | 
			
		||||
                addRelationships(importedDiagram.relationships ?? [], {
 | 
			
		||||
                    updateHistory: false,
 | 
			
		||||
                }),
 | 
			
		||||
            ]);
 | 
			
		||||
            setReorder(true);
 | 
			
		||||
            closeImportDBMLDialog();
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            toast({
 | 
			
		||||
                title: t('import_dbml_dialog.error.title'),
 | 
			
		||||
                variant: 'destructive',
 | 
			
		||||
                description: (
 | 
			
		||||
                    <>
 | 
			
		||||
                        <div>{t('import_dbml_dialog.error.description')}</div>
 | 
			
		||||
                        {e instanceof Error ? e.message : JSON.stringify(e)}
 | 
			
		||||
                    </>
 | 
			
		||||
                ),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }, [
 | 
			
		||||
        dbmlContent,
 | 
			
		||||
        closeImportDBMLDialog,
 | 
			
		||||
        tables,
 | 
			
		||||
        relationships,
 | 
			
		||||
        removeTables,
 | 
			
		||||
        removeRelationships,
 | 
			
		||||
        addTables,
 | 
			
		||||
        addRelationships,
 | 
			
		||||
        errorMessage,
 | 
			
		||||
        toast,
 | 
			
		||||
        setReorder,
 | 
			
		||||
        t,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Dialog
 | 
			
		||||
            {...dialog}
 | 
			
		||||
            onOpenChange={(open) => {
 | 
			
		||||
                if (!open) {
 | 
			
		||||
                    closeImportDBMLDialog();
 | 
			
		||||
                }
 | 
			
		||||
            }}
 | 
			
		||||
        >
 | 
			
		||||
            <DialogContent
 | 
			
		||||
                className="flex h-[80vh] max-h-screen flex-col"
 | 
			
		||||
                showClose
 | 
			
		||||
            >
 | 
			
		||||
                <DialogHeader>
 | 
			
		||||
                    <DialogTitle>
 | 
			
		||||
                        {withCreateEmptyDiagram
 | 
			
		||||
                            ? t('import_dbml_dialog.example_title')
 | 
			
		||||
                            : t('import_dbml_dialog.title')}
 | 
			
		||||
                    </DialogTitle>
 | 
			
		||||
                    <DialogDescription>
 | 
			
		||||
                        {t('import_dbml_dialog.description')}
 | 
			
		||||
                    </DialogDescription>
 | 
			
		||||
                </DialogHeader>
 | 
			
		||||
                <DialogInternalContent>
 | 
			
		||||
                    <Suspense fallback={<Spinner />}>
 | 
			
		||||
                        <Editor
 | 
			
		||||
                            value={dbmlContent}
 | 
			
		||||
                            onChange={(value) => setDBMLContent(value || '')}
 | 
			
		||||
                            language="dbml"
 | 
			
		||||
                            onMount={handleEditorDidMount}
 | 
			
		||||
                            theme={
 | 
			
		||||
                                effectiveTheme === 'dark'
 | 
			
		||||
                                    ? 'dbml-dark'
 | 
			
		||||
                                    : 'dbml-light'
 | 
			
		||||
                            }
 | 
			
		||||
                            beforeMount={setupDBMLLanguage}
 | 
			
		||||
                            options={{
 | 
			
		||||
                                minimap: { enabled: false },
 | 
			
		||||
                                scrollBeyondLastLine: false,
 | 
			
		||||
                                automaticLayout: true,
 | 
			
		||||
                                glyphMargin: true,
 | 
			
		||||
                                lineNumbers: 'on',
 | 
			
		||||
                                scrollbar: {
 | 
			
		||||
                                    vertical: 'visible',
 | 
			
		||||
                                    horizontal: 'visible',
 | 
			
		||||
                                },
 | 
			
		||||
                            }}
 | 
			
		||||
                            className="size-full"
 | 
			
		||||
                        />
 | 
			
		||||
                    </Suspense>
 | 
			
		||||
                </DialogInternalContent>
 | 
			
		||||
                <DialogFooter>
 | 
			
		||||
                    <div className="flex w-full items-center justify-between">
 | 
			
		||||
                        <div className="flex items-center gap-4">
 | 
			
		||||
                            <DialogClose asChild>
 | 
			
		||||
                                <Button variant="secondary">
 | 
			
		||||
                                    {withCreateEmptyDiagram
 | 
			
		||||
                                        ? t('import_dbml_dialog.skip_and_empty')
 | 
			
		||||
                                        : t('import_dbml_dialog.cancel')}
 | 
			
		||||
                                </Button>
 | 
			
		||||
                            </DialogClose>
 | 
			
		||||
                            {errorMessage ? (
 | 
			
		||||
                                <div className="flex items-center gap-1">
 | 
			
		||||
                                    <AlertCircle className="size-4 text-destructive" />
 | 
			
		||||
 | 
			
		||||
                                    <span className="text-xs text-destructive">
 | 
			
		||||
                                        {errorMessage ||
 | 
			
		||||
                                            t(
 | 
			
		||||
                                                'import_dbml_dialog.error.description'
 | 
			
		||||
                                            )}
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            ) : null}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <Button
 | 
			
		||||
                            onClick={handleImport}
 | 
			
		||||
                            disabled={!dbmlContent.trim() || !!errorMessage}
 | 
			
		||||
                        >
 | 
			
		||||
                            {withCreateEmptyDiagram
 | 
			
		||||
                                ? t('import_dbml_dialog.show_example')
 | 
			
		||||
                                : t('import_dbml_dialog.import')}
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </DialogFooter>
 | 
			
		||||
            </DialogContent>
 | 
			
		||||
        </Dialog>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -22,10 +22,11 @@ import { useConfig } from '@/hooks/use-config';
 | 
			
		||||
import { useDialog } from '@/hooks/use-dialog';
 | 
			
		||||
import { useStorage } from '@/hooks/use-storage';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import React, { useCallback, useEffect, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useNavigate } from 'react-router-dom';
 | 
			
		||||
import type { BaseDialogProps } from '../common/base-dialog-props';
 | 
			
		||||
import { useDebounce } from '@/hooks/use-debounce';
 | 
			
		||||
 | 
			
		||||
export interface OpenDiagramDialogProps extends BaseDialogProps {}
 | 
			
		||||
 | 
			
		||||
@@ -58,12 +59,65 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
        fetchDiagrams();
 | 
			
		||||
    }, [listDiagrams, setDiagrams, dialog.open]);
 | 
			
		||||
 | 
			
		||||
    const openDiagram = (diagramId: string) => {
 | 
			
		||||
    const openDiagram = useCallback(
 | 
			
		||||
        (diagramId: string) => {
 | 
			
		||||
            if (diagramId) {
 | 
			
		||||
                updateConfig({ defaultDiagramId: diagramId });
 | 
			
		||||
                navigate(`/diagrams/${diagramId}`);
 | 
			
		||||
            }
 | 
			
		||||
    };
 | 
			
		||||
        },
 | 
			
		||||
        [updateConfig, navigate]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const handleRowKeyDown = useCallback(
 | 
			
		||||
        (e: React.KeyboardEvent<HTMLTableRowElement>) => {
 | 
			
		||||
            const element = e.target as HTMLElement;
 | 
			
		||||
            const diagramId = element.getAttribute('data-diagram-id');
 | 
			
		||||
            const selectionIndexAttr = element.getAttribute(
 | 
			
		||||
                'data-selection-index'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (!diagramId || !selectionIndexAttr) return;
 | 
			
		||||
 | 
			
		||||
            const selectionIndex = parseInt(selectionIndexAttr, 10);
 | 
			
		||||
 | 
			
		||||
            switch (e.key) {
 | 
			
		||||
                case 'Enter':
 | 
			
		||||
                case ' ':
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                    openDiagram(diagramId);
 | 
			
		||||
                    closeOpenDiagramDialog();
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'ArrowDown': {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
 | 
			
		||||
                    (
 | 
			
		||||
                        document.querySelector(
 | 
			
		||||
                            `[data-selection-index="${selectionIndex + 1}"]`
 | 
			
		||||
                        ) as HTMLElement
 | 
			
		||||
                    )?.focus();
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case 'ArrowUp': {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
 | 
			
		||||
                    (
 | 
			
		||||
                        document.querySelector(
 | 
			
		||||
                            `[data-selection-index="${selectionIndex - 1}"]`
 | 
			
		||||
                        ) as HTMLElement
 | 
			
		||||
                    )?.focus();
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        [openDiagram, closeOpenDiagramDialog]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const onFocusHandler = useDebounce(
 | 
			
		||||
        (diagramId: string) => setSelectedDiagramId(diagramId),
 | 
			
		||||
        50
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Dialog
 | 
			
		||||
            {...dialog}
 | 
			
		||||
@@ -74,7 +128,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
            }}
 | 
			
		||||
        >
 | 
			
		||||
            <DialogContent
 | 
			
		||||
                className="flex h-[30rem] max-h-screen w-[90vw] flex-col overflow-y-auto md:w-screen xl:min-w-[55vw]"
 | 
			
		||||
                className="flex h-[30rem] max-h-screen flex-col overflow-y-auto md:min-w-[80vw] xl:min-w-[55vw]"
 | 
			
		||||
                showClose
 | 
			
		||||
            >
 | 
			
		||||
                <DialogHeader>
 | 
			
		||||
@@ -112,10 +166,17 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
                                </TableRow>
 | 
			
		||||
                            </TableHeader>
 | 
			
		||||
                            <TableBody>
 | 
			
		||||
                                {diagrams.map((diagram) => (
 | 
			
		||||
                                {diagrams.map((diagram, index) => (
 | 
			
		||||
                                    <TableRow
 | 
			
		||||
                                        key={diagram.id}
 | 
			
		||||
                                        data-state={`${selectedDiagramId === diagram.id ? 'selected' : ''}`}
 | 
			
		||||
                                        data-diagram-id={diagram.id}
 | 
			
		||||
                                        data-selection-index={index}
 | 
			
		||||
                                        tabIndex={0}
 | 
			
		||||
                                        onFocus={() =>
 | 
			
		||||
                                            onFocusHandler(diagram.id)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        className="focus:bg-accent focus:outline-none"
 | 
			
		||||
                                        onClick={(e) => {
 | 
			
		||||
                                            switch (e.detail) {
 | 
			
		||||
                                                case 1:
 | 
			
		||||
@@ -133,11 +194,17 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
                                                    );
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }}
 | 
			
		||||
                                        onKeyDown={handleRowKeyDown}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <TableCell className="table-cell">
 | 
			
		||||
                                            <div className="flex justify-center">
 | 
			
		||||
                                                <DiagramIcon
 | 
			
		||||
                                                    diagram={diagram}
 | 
			
		||||
                                                    databaseType={
 | 
			
		||||
                                                        diagram.databaseType
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                    databaseEdition={
 | 
			
		||||
                                                        diagram.databaseEdition
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                />
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </TableCell>
 | 
			
		||||
 
 | 
			
		||||
@@ -73,3 +73,68 @@
 | 
			
		||||
        @apply dark:group-hover:bg-slate-900 group-hover:bg-slate-100 group-hover:ring-[0.5px] rounded-md cursor-pointer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.gradient-background {
 | 
			
		||||
    /* Fallback: Set a background color. */
 | 
			
		||||
    background-color: #f46b24;
 | 
			
		||||
 | 
			
		||||
    /* Create the gradient. */
 | 
			
		||||
    background-image: linear-gradient(
 | 
			
		||||
        45deg,
 | 
			
		||||
        #2e6579 20%,
 | 
			
		||||
        #4fafca 20%,
 | 
			
		||||
        #4fafca 40%,
 | 
			
		||||
        #6dc630 40%,
 | 
			
		||||
        #6dc630 60%,
 | 
			
		||||
        #f9dc3a 60%,
 | 
			
		||||
        #f9dc3a 80%,
 | 
			
		||||
        #f46b24 80%
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /* Set the background size and repeat properties. */
 | 
			
		||||
    background-size: 100%;
 | 
			
		||||
    background-repeat: repeat;
 | 
			
		||||
 | 
			
		||||
    /* Use the text as a mask for the background. */
 | 
			
		||||
    /* This will show the gradient as a text color rather than element bg. */
 | 
			
		||||
    /* -webkit-background-clip: text;
 | 
			
		||||
    -webkit-text-fill-color: transparent; */
 | 
			
		||||
 | 
			
		||||
    /* Animate the text when loading the element. */
 | 
			
		||||
    /* This animates it on page load and when hovering out. */
 | 
			
		||||
    animation: rainbow-text-simple-animation-rev 0.75s ease forwards;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.gradient-background:hover {
 | 
			
		||||
    animation: rainbow-text-simple-animation 0.5s ease-in forwards;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dbml-error-line {
 | 
			
		||||
    background-color: rgba(255, 0, 0, 0.2) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes rainbow-text-simple-animation-rev {
 | 
			
		||||
    0% {
 | 
			
		||||
        background-size: 650%;
 | 
			
		||||
    }
 | 
			
		||||
    40% {
 | 
			
		||||
        background-size: 650%;
 | 
			
		||||
    }
 | 
			
		||||
    100% {
 | 
			
		||||
        background-size: 100%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Move the background and make it larger. */
 | 
			
		||||
/* Animation shown when hovering over the text. */
 | 
			
		||||
@keyframes rainbow-text-simple-animation {
 | 
			
		||||
    0% {
 | 
			
		||||
        background-size: 100%;
 | 
			
		||||
    }
 | 
			
		||||
    80% {
 | 
			
		||||
        background-size: 650%;
 | 
			
		||||
    }
 | 
			
		||||
    100% {
 | 
			
		||||
        background-size: 650%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								src/hooks/use-canvas.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,4 @@
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
import { canvasContext } from '@/context/canvas-context/canvas-context';
 | 
			
		||||
 | 
			
		||||
export const useCanvas = () => useContext(canvasContext);
 | 
			
		||||
							
								
								
									
										21
									
								
								src/hooks/use-debounce.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
import { useCallback, useRef } from 'react';
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
type AnyFunction = (...args: any[]) => any;
 | 
			
		||||
 | 
			
		||||
export const useDebounce = <T extends AnyFunction>(
 | 
			
		||||
    func: T,
 | 
			
		||||
    delay: number
 | 
			
		||||
): ((...args: Parameters<T>) => void) => {
 | 
			
		||||
    const inDebounce = useRef<NodeJS.Timeout>();
 | 
			
		||||
 | 
			
		||||
    const debounce = useCallback(
 | 
			
		||||
        (...args: Parameters<T>) => {
 | 
			
		||||
            clearTimeout(inDebounce.current);
 | 
			
		||||
            inDebounce.current = setTimeout(() => func(...args), delay);
 | 
			
		||||
        },
 | 
			
		||||
        [func, delay]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return debounce;
 | 
			
		||||
};
 | 
			
		||||
@@ -19,8 +19,10 @@ import { mr, mrMetadata } from './locales/mr';
 | 
			
		||||
import { tr, trMetadata } from './locales/tr';
 | 
			
		||||
import { id_ID, id_IDMetadata } from './locales/id_ID';
 | 
			
		||||
import { te, teMetadata } from './locales/te';
 | 
			
		||||
import { bn, bnMetadata } from './locales/bn';
 | 
			
		||||
import { gu, guMetadata } from './locales/gu';
 | 
			
		||||
import { vi, viMetadata } from './locales/vi';
 | 
			
		||||
import { ar, arMetadata } from './locales/ar';
 | 
			
		||||
 | 
			
		||||
export const languages: LanguageMetadata[] = [
 | 
			
		||||
    enMetadata,
 | 
			
		||||
@@ -40,8 +42,10 @@ export const languages: LanguageMetadata[] = [
 | 
			
		||||
    trMetadata,
 | 
			
		||||
    id_IDMetadata,
 | 
			
		||||
    teMetadata,
 | 
			
		||||
    bnMetadata,
 | 
			
		||||
    guMetadata,
 | 
			
		||||
    viMetadata,
 | 
			
		||||
    arMetadata,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const resources = {
 | 
			
		||||
@@ -62,8 +66,10 @@ const resources = {
 | 
			
		||||
    tr,
 | 
			
		||||
    id_ID,
 | 
			
		||||
    te,
 | 
			
		||||
    bn,
 | 
			
		||||
    gu,
 | 
			
		||||
    vi,
 | 
			
		||||
    ar,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
i18n.use(LanguageDetector)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										427
									
								
								src/i18n/locales/ar.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,427 @@
 | 
			
		||||
import type { LanguageMetadata, LanguageTranslation } from '../types';
 | 
			
		||||
 | 
			
		||||
export const ar: LanguageTranslation = {
 | 
			
		||||
    translation: {
 | 
			
		||||
        menu: {
 | 
			
		||||
            file: {
 | 
			
		||||
                file: 'ملف',
 | 
			
		||||
                new: 'جديد',
 | 
			
		||||
                open: 'فتح',
 | 
			
		||||
                save: 'حفظ',
 | 
			
		||||
                import: 'استيراد قاعدة بيانات',
 | 
			
		||||
                export_sql: 'SQL تصدير',
 | 
			
		||||
                export_as: 'تصدير كـ',
 | 
			
		||||
                delete_diagram: 'حذف الرسم البياني',
 | 
			
		||||
                exit: 'خروج',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'تحرير',
 | 
			
		||||
                undo: 'تراجع',
 | 
			
		||||
                redo: 'إعادة',
 | 
			
		||||
                clear: 'مسح',
 | 
			
		||||
            },
 | 
			
		||||
            view: {
 | 
			
		||||
                view: 'عرض',
 | 
			
		||||
                show_sidebar: 'إظهار الشريط الجانبي',
 | 
			
		||||
                hide_sidebar: 'إخفاء الشريط الجانبي',
 | 
			
		||||
                hide_cardinality: 'إخفاء الكاردينالية',
 | 
			
		||||
                show_cardinality: 'إظهار الكاردينالية',
 | 
			
		||||
                zoom_on_scroll: 'تكبير/تصغير عند التمرير',
 | 
			
		||||
                theme: 'المظهر',
 | 
			
		||||
                show_dependencies: 'إظهار الاعتمادات',
 | 
			
		||||
                hide_dependencies: 'إخفاء الاعتمادات',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_minimap: 'Show Mini Map',
 | 
			
		||||
                hide_minimap: 'Hide Mini Map',
 | 
			
		||||
            },
 | 
			
		||||
            share: {
 | 
			
		||||
                share: 'مشاركة',
 | 
			
		||||
                export_diagram: 'تصدير المخطط',
 | 
			
		||||
                import_diagram: 'استيراد المخطط',
 | 
			
		||||
            },
 | 
			
		||||
            help: {
 | 
			
		||||
                help: 'مساعدة',
 | 
			
		||||
                visit_website: 'ChartDB قم بزيارة',
 | 
			
		||||
                join_discord: 'Discord انضم إلينا على',
 | 
			
		||||
                schedule_a_call: '!تحدث معنا',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        delete_diagram_alert: {
 | 
			
		||||
            title: 'حذف المخطط',
 | 
			
		||||
            description:
 | 
			
		||||
                '.لا يمكن التراجع عن هذا الإجراء. سيتم حذف الرسم البياني بشكل دائم',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            delete: 'حذف',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        clear_diagram_alert: {
 | 
			
		||||
            title: 'مسح الرسم البياني',
 | 
			
		||||
            description:
 | 
			
		||||
                '.لا يمكن التراجع عن هذا الاجراء. سيتم حذف جميع البيانات في الرسم البياني بشكل دائم',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            clear: 'مسح',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'إعادة ترتيب الرسم البياني',
 | 
			
		||||
            description:
 | 
			
		||||
                'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
 | 
			
		||||
            reorder: 'إعادة ترتيب',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        multiple_schemas_alert: {
 | 
			
		||||
            title: 'مخططات متعددة',
 | 
			
		||||
            description:
 | 
			
		||||
                '{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
 | 
			
		||||
            dont_show_again: 'لا تظهره مجدداً',
 | 
			
		||||
            change_schema: 'تغيير',
 | 
			
		||||
            none: 'لا شيء',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        copy_to_clipboard_toast: {
 | 
			
		||||
            unsupported: {
 | 
			
		||||
                title: 'فشل النسخ',
 | 
			
		||||
                description: '.الحافظة غير مدعومة',
 | 
			
		||||
            },
 | 
			
		||||
            failed: {
 | 
			
		||||
                title: 'فشل النسخ',
 | 
			
		||||
                description: 'حدث خطأ أثناء النسخ. حاول مجدداً',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        theme: {
 | 
			
		||||
            system: 'النظام',
 | 
			
		||||
            light: 'فاتح',
 | 
			
		||||
            dark: 'داكن',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        zoom: {
 | 
			
		||||
            on: 'تشغيل',
 | 
			
		||||
            off: 'إيقاف',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        last_saved: 'آخر حفظ',
 | 
			
		||||
        saved: 'تم الحفظ',
 | 
			
		||||
        loading_diagram: '...جارِ تحميل الرسم البياني',
 | 
			
		||||
        deselect_all: 'إلغاء تحديد الكل',
 | 
			
		||||
        select_all: 'تحديد الكل',
 | 
			
		||||
        clear: 'مسح',
 | 
			
		||||
        show_more: 'عرض المزيد',
 | 
			
		||||
        show_less: 'عرض أقل',
 | 
			
		||||
        copy_to_clipboard: 'نسخ إلى الحافظة',
 | 
			
		||||
        copied: '!تم النسخ',
 | 
			
		||||
 | 
			
		||||
        side_panel: {
 | 
			
		||||
            schema: ':المخطط',
 | 
			
		||||
            filter_by_schema: 'تصفية حسب المخطط',
 | 
			
		||||
            search_schema: '...بحث في المخطط',
 | 
			
		||||
            no_schemas_found: '.لم يتم العثور على مخططات',
 | 
			
		||||
            view_all_options: '...عرض جميع الخيارات',
 | 
			
		||||
            tables_section: {
 | 
			
		||||
                tables: 'الجداول',
 | 
			
		||||
                add_table: 'إضافة جدول',
 | 
			
		||||
                filter: 'تصفية',
 | 
			
		||||
                collapse: 'طي الكل',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                clear: 'Clear Filter',
 | 
			
		||||
                no_results: 'No tables found matching your filter.',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_list: 'Show Table List',
 | 
			
		||||
                show_dbml: 'Show DBML Editor',
 | 
			
		||||
 | 
			
		||||
                table: {
 | 
			
		||||
                    fields: 'الحقول',
 | 
			
		||||
                    nullable: 'يمكن ان يكون فارغاً؟',
 | 
			
		||||
                    primary_key: 'المفتاح الأساسي',
 | 
			
		||||
                    indexes: 'الفهارس',
 | 
			
		||||
                    comments: 'تعليقات',
 | 
			
		||||
                    no_comments: 'لا توجد تعليقات',
 | 
			
		||||
                    add_field: 'إضافة حقل',
 | 
			
		||||
                    add_index: 'إضافة فهرس',
 | 
			
		||||
                    index_select_fields: 'حدد الحقول',
 | 
			
		||||
                    no_types_found: 'لا يوجد أنواع',
 | 
			
		||||
                    field_name: 'الإسم',
 | 
			
		||||
                    field_type: 'النوع',
 | 
			
		||||
                    field_actions: {
 | 
			
		||||
                        title: 'خصائص الحقل',
 | 
			
		||||
                        unique: 'فريد',
 | 
			
		||||
                        comments: 'تعليقات',
 | 
			
		||||
                        no_comments: 'لا يوجد تعليقات',
 | 
			
		||||
                        delete_field: 'حذف الحقل',
 | 
			
		||||
                    },
 | 
			
		||||
                    index_actions: {
 | 
			
		||||
                        title: 'خصائص الفهرس',
 | 
			
		||||
                        name: 'الإسم',
 | 
			
		||||
                        unique: 'فريد',
 | 
			
		||||
                        delete_index: 'حذف الفهرس',
 | 
			
		||||
                    },
 | 
			
		||||
                    table_actions: {
 | 
			
		||||
                        title: 'إجراءات الجدول',
 | 
			
		||||
                        change_schema: 'تغيير المخطط',
 | 
			
		||||
                        add_field: 'إضافة حقل',
 | 
			
		||||
                        add_index: 'إضافة فهرس',
 | 
			
		||||
                        duplicate_table: 'نسخ الجدول',
 | 
			
		||||
                        delete_table: 'حذف الجدول',
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                empty_state: {
 | 
			
		||||
                    title: 'لا توجد جداول',
 | 
			
		||||
                    description: 'أنشئ جدولاً للبدء',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            relationships_section: {
 | 
			
		||||
                relationships: 'العلاقات',
 | 
			
		||||
                filter: 'تصفية',
 | 
			
		||||
                add_relationship: 'إضافة علاقة',
 | 
			
		||||
                collapse: 'طي الكل',
 | 
			
		||||
                relationship: {
 | 
			
		||||
                    primary: 'الجدول الأساسي',
 | 
			
		||||
                    foreign: 'الجدول المرتبط',
 | 
			
		||||
                    cardinality: 'الكاردينالية',
 | 
			
		||||
                    delete_relationship: 'حذف',
 | 
			
		||||
                    relationship_actions: {
 | 
			
		||||
                        title: 'إجراءات',
 | 
			
		||||
                        delete_relationship: 'حذف',
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                empty_state: {
 | 
			
		||||
                    title: 'لا توجد علاقات',
 | 
			
		||||
                    description: 'إنشئ علاقة لربط الجداول',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            dependencies_section: {
 | 
			
		||||
                dependencies: 'الاعتمادات',
 | 
			
		||||
                filter: 'تصفية',
 | 
			
		||||
                collapse: 'طي الكل',
 | 
			
		||||
                dependency: {
 | 
			
		||||
                    table: 'الجدول',
 | 
			
		||||
                    dependent_table: 'عرض الاعتمادات',
 | 
			
		||||
                    delete_dependency: 'حذف',
 | 
			
		||||
                    dependency_actions: {
 | 
			
		||||
                        title: 'إجراءات',
 | 
			
		||||
                        delete_dependency: 'حذف',
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                empty_state: {
 | 
			
		||||
                    title: 'لا توجد اعتمادات',
 | 
			
		||||
                    description: 'إنشاء اعتماد للبدء',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        toolbar: {
 | 
			
		||||
            zoom_in: 'تكبير',
 | 
			
		||||
            zoom_out: 'تصغير',
 | 
			
		||||
            save: 'حفظ',
 | 
			
		||||
            show_all: 'عرض الكل',
 | 
			
		||||
            undo: 'تراجع',
 | 
			
		||||
            redo: 'إعادة',
 | 
			
		||||
            reorder_diagram: 'إعادة ترتيب الرسم البياني',
 | 
			
		||||
            highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        new_diagram_dialog: {
 | 
			
		||||
            database_selection: {
 | 
			
		||||
                title: 'ما هو نوع قاعدة البيانات الخاصة بك؟',
 | 
			
		||||
                description:
 | 
			
		||||
                    'تتمتع كل قاعدة بيانات بمميزاتها وقدراتها الفريدة.',
 | 
			
		||||
                check_examples_long: 'ألقي نظرة على الأمثلة',
 | 
			
		||||
                check_examples_short: 'أمثلة',
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            import_database: {
 | 
			
		||||
                title: 'إسترد قاعدة بياناتك',
 | 
			
		||||
                database_edition: ':إصدار قاعدة البيانات',
 | 
			
		||||
                step_1: ':قم بتشغيل هذا البرنامج النصي في قاعدة بياناتك',
 | 
			
		||||
                step_2: ':إلصق نتيجة البرنامج النصي هنا',
 | 
			
		||||
                script_results_placeholder: '...نتيجة البرنامج النصي هنا',
 | 
			
		||||
                ssms_instructions: {
 | 
			
		||||
                    button_text: 'SSMS تعليمات',
 | 
			
		||||
                    title: 'تعليمات',
 | 
			
		||||
                    step_1: 'SQL SERVER < انتقل إلى الأدوات > الخيارات > نتائح الاستعلام',
 | 
			
		||||
                    step_2: '(اضبطها على 9999999) XML اذا كنت تستخدم "نتائج إلى الشبكة"، قم بتغيير الحد الاقصى للاحرف المستردة للبيانات غير',
 | 
			
		||||
                },
 | 
			
		||||
                instructions_link: 'تحتاج مساعدة؟ شاهد الفيديو',
 | 
			
		||||
                check_script_result: 'تحقق من نتيجة البرنامج النصي',
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            import_from_file: 'استيراد من ملف',
 | 
			
		||||
            back: 'رجوع',
 | 
			
		||||
            empty_diagram: 'مخطط فارغ',
 | 
			
		||||
            continue: 'متابعة',
 | 
			
		||||
            import: 'استيراد',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'فتح مخطط',
 | 
			
		||||
            description: 'اختر مخططًا لفتحه من القائمة ادناه',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'الإسم',
 | 
			
		||||
                created_at: 'تاريخ الإنشاء',
 | 
			
		||||
                last_modified: 'آخر تعديل',
 | 
			
		||||
                tables_count: 'الجداول',
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            open: 'فتح',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
            title: 'SQL تصدير',
 | 
			
		||||
            description:
 | 
			
		||||
                '{{databaseType}} صدّر مخطط الرسم البياني إلى برنامج نصي لـ',
 | 
			
		||||
            close: 'إغلاق',
 | 
			
		||||
            loading: {
 | 
			
		||||
                text: '...{{databaseType}} ل SQL يقوم الذكاء الاصطناعي بإنشاء',
 | 
			
		||||
                description: 'هذا قد يستغرق 30 ثانية',
 | 
			
		||||
            },
 | 
			
		||||
            error: {
 | 
			
		||||
                message:
 | 
			
		||||
                    'النصي. يرجى المحاولة مرة اخرى لاحقاً او <0>اتصل بنا</0> SQL خطأ في إنشاء برنامج',
 | 
			
		||||
                description:
 | 
			
		||||
                    ' الخاصة بك. راجع الدليل <0>هنا</0> OPENAI_TOKEN لا تتردد في استخدام',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        create_relationship_dialog: {
 | 
			
		||||
            title: 'إنشاء علاقة',
 | 
			
		||||
            primary_table: 'الجدول الأساسي',
 | 
			
		||||
            primary_field: 'الحقل الأساسي',
 | 
			
		||||
            referenced_table: 'الجدول المرتبط',
 | 
			
		||||
            referenced_field: 'الحقل المرتبط',
 | 
			
		||||
            primary_table_placeholder: 'حدد الجدول',
 | 
			
		||||
            primary_field_placeholder: 'حدد الحقل',
 | 
			
		||||
            referenced_table_placeholder: 'حدد الجدول',
 | 
			
		||||
            referenced_field_placeholder: 'حدد الحقل',
 | 
			
		||||
            no_tables_found: 'لم يتم العثور على جداول',
 | 
			
		||||
            no_fields_found: 'لم يتم العثور على حقول',
 | 
			
		||||
            create: 'إنشاء',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        import_database_dialog: {
 | 
			
		||||
            title: 'استيراد إلى المخطط الحالي',
 | 
			
		||||
            override_alert: {
 | 
			
		||||
                title: 'استيراد قاعدة بيانات',
 | 
			
		||||
                content: {
 | 
			
		||||
                    alert: 'سيؤدي استيراد هذا المخطط إلى التأثير على الجداول والعلاقات الحالية.',
 | 
			
		||||
                    new_tables:
 | 
			
		||||
                        'جداول جديدة <bold>{{newTablesNumber}}</bold> سيتم إضافة',
 | 
			
		||||
                    new_relationships:
 | 
			
		||||
                        'علاقات جديدة <bold>{{newRelationshipsNumber}}</bold> سيتم إنشاء',
 | 
			
		||||
                    tables_override:
 | 
			
		||||
                        'جداول <bold>{{tablesOverrideNumber}}</bold> سيتم تعديل',
 | 
			
		||||
                    proceed: 'هل تريد المتابعة؟',
 | 
			
		||||
                },
 | 
			
		||||
                import: 'استيراد',
 | 
			
		||||
                cancel: 'إلغاء',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_image_dialog: {
 | 
			
		||||
            title: 'تصدير الصورة',
 | 
			
		||||
            description: ':اختر عامل المقياس للتصدير',
 | 
			
		||||
            scale_1x: '1x عادي',
 | 
			
		||||
            scale_2x: '2x (موصى به)',
 | 
			
		||||
            scale_3x: '3x',
 | 
			
		||||
            scale_4x: '4x',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            export: 'تصدير',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        new_table_schema_dialog: {
 | 
			
		||||
            title: 'اختر مخططاً',
 | 
			
		||||
            description:
 | 
			
		||||
                '.يتم حالياً عرض مخططات متعددة. اختر واحداً للجدول الجديد',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            confirm: 'تأكيد',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        update_table_schema_dialog: {
 | 
			
		||||
            title: 'تغيير المخطط',
 | 
			
		||||
            description: '"{{tableName}}" تحديث مخطط الجدول',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            confirm: 'تغيير',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        star_us_dialog: {
 | 
			
		||||
            title: '!ساعدنا على التحسن',
 | 
			
		||||
            description: '؟! إنها مجرد نقرة واحدةGITHUB هل ترغب في تقييمنا على',
 | 
			
		||||
            close: 'ليس الآن',
 | 
			
		||||
            confirm: '!بالتأكيد',
 | 
			
		||||
        },
 | 
			
		||||
        export_diagram_dialog: {
 | 
			
		||||
            title: 'تصدير المخطط',
 | 
			
		||||
            description: ':اختر التنسيق للتصدير',
 | 
			
		||||
            format_json: 'JSON',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            export: 'تصدير',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'حدث خطأ أثناء التصدير',
 | 
			
		||||
                description:
 | 
			
		||||
                    'chartdb.io@gmail.com حدث خطأ ما. هل تحتاج إلى مساعدة؟',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        import_diagram_dialog: {
 | 
			
		||||
            title: 'استيراد الرسم البياني',
 | 
			
		||||
            description: ':للرسم البياني ادناه JSON قم بلصق',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            import: 'استيراد',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'حدث خطأ أثناء الاستيراد',
 | 
			
		||||
                description:
 | 
			
		||||
                    'chartdb.io@gmail.com و المحاولة مرة اخرى. هل تحتاج إلى المساعدة؟ JSON غير صالح. يرجى التحقق من JSON الرسم البياني',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        import_dbml_dialog: {
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            title: 'Import DBML',
 | 
			
		||||
            example_title: 'Import Example DBML',
 | 
			
		||||
            description: 'Import a database schema from DBML format.',
 | 
			
		||||
            import: 'Import',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            skip_and_empty: 'Skip & Empty',
 | 
			
		||||
            show_example: 'Show Example',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Error',
 | 
			
		||||
                description: 'Failed to parse DBML. Please check the syntax.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        relationship_type: {
 | 
			
		||||
            one_to_one: 'واحد إلى واحد',
 | 
			
		||||
            one_to_many: 'واحد إلى متعدد',
 | 
			
		||||
            many_to_one: 'متعدد إلى واحد',
 | 
			
		||||
            many_to_many: 'متعدد إلى متعدد',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        canvas_context_menu: {
 | 
			
		||||
            new_table: 'جدول جديد',
 | 
			
		||||
            new_relationship: 'علاقة جديدة',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        table_node_context_menu: {
 | 
			
		||||
            edit_table: 'تعديل الجدول',
 | 
			
		||||
            duplicate_table: 'نسخ الجدول',
 | 
			
		||||
            delete_table: 'حذف الجدول',
 | 
			
		||||
            add_relationship: 'Add Relationship', // TODO: Translate
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        snap_to_grid_tooltip: '({{key}} مغنظة الشبكة (اضغط مع الاستمرار على',
 | 
			
		||||
 | 
			
		||||
        tool_tips: {
 | 
			
		||||
            double_click_to_edit: 'انقر مرتين للتعديل',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        language_select: {
 | 
			
		||||
            change_language: 'اللغة',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const arMetadata: LanguageMetadata = {
 | 
			
		||||
    name: 'Arabic',
 | 
			
		||||
    nativeName: 'العربية',
 | 
			
		||||
    code: 'ar',
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										431
									
								
								src/i18n/locales/bn.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,431 @@
 | 
			
		||||
import type { LanguageMetadata, LanguageTranslation } from '../types';
 | 
			
		||||
 | 
			
		||||
export const bn: LanguageTranslation = {
 | 
			
		||||
    translation: {
 | 
			
		||||
        menu: {
 | 
			
		||||
            file: {
 | 
			
		||||
                file: 'ফাইল',
 | 
			
		||||
                new: 'নতুন',
 | 
			
		||||
                open: 'খুলুন',
 | 
			
		||||
                save: 'সংরক্ষণ করুন',
 | 
			
		||||
                import: 'ডাটাবেস আমদানি করুন',
 | 
			
		||||
                export_sql: 'SQL রপ্তানি করুন',
 | 
			
		||||
                export_as: 'রূপে রপ্তানি করুন',
 | 
			
		||||
                delete_diagram: 'ডায়াগ্রাম মুছুন',
 | 
			
		||||
                exit: 'প্রস্থান করুন',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'সম্পাদনা',
 | 
			
		||||
                undo: 'পূর্বাবস্থায় ফিরুন',
 | 
			
		||||
                redo: 'পুনরায় করুন',
 | 
			
		||||
                clear: 'পরিষ্কার করুন',
 | 
			
		||||
            },
 | 
			
		||||
            view: {
 | 
			
		||||
                view: 'দেখুন',
 | 
			
		||||
                show_sidebar: 'সাইডবার দেখান',
 | 
			
		||||
                hide_sidebar: 'সাইডবার লুকান',
 | 
			
		||||
                hide_cardinality: 'কার্ডিনালিটি লুকান',
 | 
			
		||||
                show_cardinality: 'কার্ডিনালিটি দেখান',
 | 
			
		||||
                zoom_on_scroll: 'স্ক্রলে জুম করুন',
 | 
			
		||||
                theme: 'থিম',
 | 
			
		||||
                show_dependencies: 'নির্ভরতাগুলি দেখান',
 | 
			
		||||
                hide_dependencies: 'নির্ভরতাগুলি লুকান',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_minimap: 'Show Mini Map',
 | 
			
		||||
                hide_minimap: 'Hide Mini Map',
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            share: {
 | 
			
		||||
                share: 'শেয়ার করুন',
 | 
			
		||||
                export_diagram: 'ডায়াগ্রাম রপ্তানি করুন',
 | 
			
		||||
                import_diagram: 'ডায়াগ্রাম আমদানি করুন',
 | 
			
		||||
            },
 | 
			
		||||
            help: {
 | 
			
		||||
                help: 'সাহায্য',
 | 
			
		||||
                visit_website: 'ChartDB ওয়েবসাইটে যান',
 | 
			
		||||
                join_discord: 'আমাদের Discord-এ যোগ দিন',
 | 
			
		||||
                schedule_a_call: 'আমাদের সাথে কথা বলুন!',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        delete_diagram_alert: {
 | 
			
		||||
            title: 'ডায়াগ্রাম মুছুন',
 | 
			
		||||
            description:
 | 
			
		||||
                'এই কাজটি পূর্বাবস্থায় ফিরিয়ে আনা যাবে না। এই ডায়াগ্রাম স্থায়ীভাবে মুছে ফেলা হবে।',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            delete: 'মুছুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        clear_diagram_alert: {
 | 
			
		||||
            title: 'ডায়াগ্রাম পরিষ্কার করুন',
 | 
			
		||||
            description:
 | 
			
		||||
                'এই কাজটি পূর্বাবস্থায় ফিরিয়ে আনা যাবে না। এই ডায়াগ্রামের সমস্ত তথ্য স্থায়ীভাবে মুছে যাবে।',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            clear: 'পরিষ্কার করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
 | 
			
		||||
            description:
 | 
			
		||||
                'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?',
 | 
			
		||||
            reorder: 'পুনর্বিন্যাস করুন',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        multiple_schemas_alert: {
 | 
			
		||||
            title: 'বহু স্কিমা',
 | 
			
		||||
            description:
 | 
			
		||||
                '{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
 | 
			
		||||
            dont_show_again: 'পুনরায় দেখাবেন না',
 | 
			
		||||
            change_schema: 'পরিবর্তন করুন',
 | 
			
		||||
            none: 'কিছুই না',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        copy_to_clipboard_toast: {
 | 
			
		||||
            unsupported: {
 | 
			
		||||
                title: 'কপি ব্যর্থ হয়েছে',
 | 
			
		||||
                description: 'ক্লিপবোর্ড সমর্থিত নয়',
 | 
			
		||||
            },
 | 
			
		||||
            failed: {
 | 
			
		||||
                title: 'কপি ব্যর্থ হয়েছে',
 | 
			
		||||
                description: 'কিছু ভুল হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        theme: {
 | 
			
		||||
            system: 'সিস্টেম',
 | 
			
		||||
            light: 'হালকা',
 | 
			
		||||
            dark: 'অন্ধকার',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        zoom: {
 | 
			
		||||
            on: 'চালু',
 | 
			
		||||
            off: 'বন্ধ',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        last_saved: 'সর্বশেষ সংরক্ষণ',
 | 
			
		||||
        saved: 'সংরক্ষিত',
 | 
			
		||||
        loading_diagram: 'ডায়াগ্রাম লোড হচ্ছে...',
 | 
			
		||||
        deselect_all: 'সব নির্বাচন সরান',
 | 
			
		||||
        select_all: 'সব নির্বাচন করুন',
 | 
			
		||||
        clear: 'পরিষ্কার করুন',
 | 
			
		||||
        show_more: 'আরও দেখুন',
 | 
			
		||||
        show_less: 'কম দেখুন',
 | 
			
		||||
        copy_to_clipboard: 'ক্লিপবোর্ডে অনুলিপি করুন',
 | 
			
		||||
        copied: 'অনুলিপি সম্পন্ন!',
 | 
			
		||||
 | 
			
		||||
        side_panel: {
 | 
			
		||||
            schema: 'স্কিমা:',
 | 
			
		||||
            filter_by_schema: 'স্কিমা দ্বারা ফিল্টার করুন',
 | 
			
		||||
            search_schema: 'স্কিমা খুঁজুন...',
 | 
			
		||||
            no_schemas_found: 'কোনো স্কিমা পাওয়া যায়নি।',
 | 
			
		||||
            view_all_options: 'সমস্ত বিকল্প দেখুন...',
 | 
			
		||||
            tables_section: {
 | 
			
		||||
                tables: 'টেবিল',
 | 
			
		||||
                add_table: 'টেবিল যোগ করুন',
 | 
			
		||||
                filter: 'ফিল্টার',
 | 
			
		||||
                collapse: 'সব ভাঁজ করুন',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                clear: 'Clear Filter',
 | 
			
		||||
                no_results: 'No tables found matching your filter.',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_list: 'Show Table List',
 | 
			
		||||
                show_dbml: 'Show DBML Editor',
 | 
			
		||||
 | 
			
		||||
                table: {
 | 
			
		||||
                    fields: 'ফিল্ড',
 | 
			
		||||
                    nullable: 'নালযোগ্য?',
 | 
			
		||||
                    primary_key: 'প্রাথমিক কী',
 | 
			
		||||
                    indexes: 'ইনডেক্স',
 | 
			
		||||
                    comments: 'মন্তব্য',
 | 
			
		||||
                    no_comments: 'কোনো মন্তব্য নেই',
 | 
			
		||||
                    add_field: 'ফিল্ড যোগ করুন',
 | 
			
		||||
                    add_index: 'ইনডেক্স যোগ করুন',
 | 
			
		||||
                    index_select_fields: 'ফিল্ড নির্বাচন করুন',
 | 
			
		||||
                    no_types_found: 'কোনো ধরন পাওয়া যায়নি',
 | 
			
		||||
                    field_name: 'নাম',
 | 
			
		||||
                    field_type: 'ধরন',
 | 
			
		||||
                    field_actions: {
 | 
			
		||||
                        title: 'ফিল্ড কর্ম',
 | 
			
		||||
                        unique: 'অদ্বিতীয়',
 | 
			
		||||
                        comments: 'মন্তব্য',
 | 
			
		||||
                        no_comments: 'কোনো মন্তব্য নেই',
 | 
			
		||||
                        delete_field: 'ফিল্ড মুছুন',
 | 
			
		||||
                    },
 | 
			
		||||
                    index_actions: {
 | 
			
		||||
                        title: 'ইনডেক্স কর্ম',
 | 
			
		||||
                        name: 'নাম',
 | 
			
		||||
                        unique: 'অদ্বিতীয়',
 | 
			
		||||
                        delete_index: 'ইনডেক্স মুছুন',
 | 
			
		||||
                    },
 | 
			
		||||
                    table_actions: {
 | 
			
		||||
                        title: 'টেবিল কর্ম',
 | 
			
		||||
                        change_schema: 'স্কিমা পরিবর্তন করুন',
 | 
			
		||||
                        add_field: 'ফিল্ড যোগ করুন',
 | 
			
		||||
                        add_index: 'ইনডেক্স যোগ করুন',
 | 
			
		||||
                        duplicate_table: 'টেবিল নকল করুন',
 | 
			
		||||
                        delete_table: 'টেবিল মুছুন',
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                empty_state: {
 | 
			
		||||
                    title: 'কোনো টেবিল নেই',
 | 
			
		||||
                    description: 'শুরু করতে একটি টেবিল তৈরি করুন',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            relationships_section: {
 | 
			
		||||
                relationships: 'সম্পর্ক',
 | 
			
		||||
                filter: 'ফিল্টার',
 | 
			
		||||
                add_relationship: 'সম্পর্ক যোগ করুন',
 | 
			
		||||
                collapse: 'সব ভাঁজ করুন',
 | 
			
		||||
                relationship: {
 | 
			
		||||
                    primary: 'প্রাথমিক টেবিল',
 | 
			
		||||
                    foreign: 'বিদেশি টেবিল',
 | 
			
		||||
                    cardinality: 'কার্ডিনালিটি',
 | 
			
		||||
                    delete_relationship: 'মুছুন',
 | 
			
		||||
                    relationship_actions: {
 | 
			
		||||
                        title: 'কর্ম',
 | 
			
		||||
                        delete_relationship: 'মুছুন',
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                empty_state: {
 | 
			
		||||
                    title: 'কোনো সম্পর্ক নেই',
 | 
			
		||||
                    description: 'টেবিল সংযোগ করতে একটি সম্পর্ক তৈরি করুন',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            dependencies_section: {
 | 
			
		||||
                dependencies: 'নির্ভরতাগুলি',
 | 
			
		||||
                filter: 'ফিল্টার',
 | 
			
		||||
                collapse: 'ভাঁজ করুন',
 | 
			
		||||
                dependency: {
 | 
			
		||||
                    table: 'টেবিল',
 | 
			
		||||
                    dependent_table: 'নির্ভরশীল টেবিল',
 | 
			
		||||
                    delete_dependency: 'নির্ভরতা মুছুন',
 | 
			
		||||
                    dependency_actions: {
 | 
			
		||||
                        title: 'কর্ম',
 | 
			
		||||
                        delete_dependency: 'নির্ভরতা মুছুন',
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                empty_state: {
 | 
			
		||||
                    title: 'কোনো নির্ভরতাগুলি নেই',
 | 
			
		||||
                    description: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        toolbar: {
 | 
			
		||||
            zoom_in: 'জুম ইন',
 | 
			
		||||
            zoom_out: 'জুম আউট',
 | 
			
		||||
            save: 'সংরক্ষণ করুন',
 | 
			
		||||
            show_all: 'সব দেখান',
 | 
			
		||||
            undo: 'পূর্বাবস্থায় ফিরুন',
 | 
			
		||||
            redo: 'পুনরায় করুন',
 | 
			
		||||
            reorder_diagram: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
 | 
			
		||||
            highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        new_diagram_dialog: {
 | 
			
		||||
            database_selection: {
 | 
			
		||||
                title: 'আপনার ডাটাবেস কী?',
 | 
			
		||||
                description:
 | 
			
		||||
                    'প্রত্যেক ডাটাবেসের নিজস্ব বৈশিষ্ট্য এবং ক্ষমতা রয়েছে।',
 | 
			
		||||
                check_examples_long: 'উদাহরণ দেখুন',
 | 
			
		||||
                check_examples_short: 'উদাহরণ',
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            import_database: {
 | 
			
		||||
                title: 'আপনার ডাটাবেস আমদানি করুন',
 | 
			
		||||
                database_edition: 'ডাটাবেস সংস্করণ:',
 | 
			
		||||
                step_1: 'আপনার ডাটাবেসে এই স্ক্রিপ্ট চালান:',
 | 
			
		||||
                step_2: 'স্ক্রিপ্টের ফলাফল এখানে পেস্ট করুন:',
 | 
			
		||||
                script_results_placeholder: 'স্ক্রিপ্টের ফলাফল এখানে...',
 | 
			
		||||
                ssms_instructions: {
 | 
			
		||||
                    button_text: 'SSMS নির্দেশনা',
 | 
			
		||||
                    title: 'নির্দেশনা',
 | 
			
		||||
                    step_1: 'টুলস > অপশন > কোয়েরি ফলাফল > SQL সার্ভারে যান।',
 | 
			
		||||
                    step_2: 'যদি আপনি "গ্রিডে ফলাফল" ব্যবহার করেন, তাহলে নন-XML ডেটার জন্য সর্বাধিক চরিত্রগুলি 9999999-এ সেট করুন।',
 | 
			
		||||
                },
 | 
			
		||||
                instructions_link: 'সাহায্যের প্রয়োজন? এখানে দেখুন',
 | 
			
		||||
                check_script_result: 'স্ক্রিপ্ট ফলাফল যাচাই করুন',
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            back: 'ফিরে যান',
 | 
			
		||||
            import_from_file: 'ফাইল থেকে আমদানি করুন',
 | 
			
		||||
            empty_diagram: 'ফাঁকা চিত্র',
 | 
			
		||||
            continue: 'চালিয়ে যান',
 | 
			
		||||
            import: 'আমদানি করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'চিত্র খুলুন',
 | 
			
		||||
            description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'নাম',
 | 
			
		||||
                created_at: 'তৈরির তারিখ',
 | 
			
		||||
                last_modified: 'সর্বশেষ পরিবর্তিত',
 | 
			
		||||
                tables_count: 'টেবিল',
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            open: 'খুলুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
            title: 'SQL রপ্তানি করুন',
 | 
			
		||||
            description:
 | 
			
		||||
                '{{databaseType}} স্ক্রিপ্টের জন্য আপনার ডায়াগ্রাম স্কিমা রপ্তানি করুন',
 | 
			
		||||
            close: 'বন্ধ করুন',
 | 
			
		||||
            loading: {
 | 
			
		||||
                text: '{{databaseType}} এর জন্য AI SQL তৈরি হচ্ছে...',
 | 
			
		||||
                description: 'এতে ৩০ সেকেন্ড পর্যন্ত সময় লাগতে পারে।',
 | 
			
		||||
            },
 | 
			
		||||
            error: {
 | 
			
		||||
                message:
 | 
			
		||||
                    'SQL স্ক্রিপ্ট তৈরি করার সময় একটি ত্রুটি ঘটেছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন বা <0>আমাদের সাথে যোগাযোগ করুন</0>।',
 | 
			
		||||
                description:
 | 
			
		||||
                    'আপনার OPENAI_TOKEN ব্যবহার করার জন্য বিনামূল্যে অভিজ্ঞতা নিন, ম্যানুয়াল <0>এখানে দেখুন</0>।',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        create_relationship_dialog: {
 | 
			
		||||
            title: 'সম্পর্ক তৈরি করুন',
 | 
			
		||||
            primary_table: 'প্রাথমিক টেবিল',
 | 
			
		||||
            primary_field: 'প্রাথমিক ক্ষেত্র',
 | 
			
		||||
            referenced_table: 'রেফারেন্স করা টেবিল',
 | 
			
		||||
            referenced_field: 'রেফারেন্স করা ক্ষেত্র',
 | 
			
		||||
            primary_table_placeholder: 'টেবিল নির্বাচন করুন',
 | 
			
		||||
            primary_field_placeholder: 'ক্ষেত্র নির্বাচন করুন',
 | 
			
		||||
            referenced_table_placeholder: 'টেবিল নির্বাচন করুন',
 | 
			
		||||
            referenced_field_placeholder: 'ক্ষেত্র নির্বাচন করুন',
 | 
			
		||||
            no_tables_found: 'কোন টেবিল পাওয়া যায়নি',
 | 
			
		||||
            no_fields_found: 'কোন ক্ষেত্র পাওয়া যায়নি',
 | 
			
		||||
            create: 'তৈরি করুন',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        import_database_dialog: {
 | 
			
		||||
            title: 'বর্তমান চিত্রে আমদানি করুন',
 | 
			
		||||
            override_alert: {
 | 
			
		||||
                title: 'ডাটাবেস আমদানি করুন',
 | 
			
		||||
                content: {
 | 
			
		||||
                    alert: 'এই চিত্র আমদানির ফলে বিদ্যমান টেবিল ও সম্পর্ক প্রভাবিত হবে।',
 | 
			
		||||
                    new_tables:
 | 
			
		||||
                        '<bold>{{newTablesNumber}}</bold> নতুন টেবিল যোগ করা হবে।',
 | 
			
		||||
                    new_relationships:
 | 
			
		||||
                        '<bold>{{newRelationshipsNumber}}</bold> নতুন সম্পর্ক তৈরি করা হবে।',
 | 
			
		||||
                    tables_override:
 | 
			
		||||
                        '<bold>{{tablesOverrideNumber}}</bold> টেবিল ওভাররাইট করা হবে।',
 | 
			
		||||
                    proceed: 'আপনি কি এগিয়ে যেতে চান?',
 | 
			
		||||
                },
 | 
			
		||||
                import: 'আমদানি করুন',
 | 
			
		||||
                cancel: 'বাতিল করুন',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_image_dialog: {
 | 
			
		||||
            title: 'চিত্র রপ্তানি করুন',
 | 
			
		||||
            description: 'রপ্তানির জন্য স্কেল ফ্যাক্টর নির্বাচন করুন:',
 | 
			
		||||
            scale_1x: '1x স্বাভাবিক',
 | 
			
		||||
            scale_2x: '2x (প্রস্তাবিত)',
 | 
			
		||||
            scale_3x: '3x',
 | 
			
		||||
            scale_4x: '4x',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            export: 'রপ্তানি করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        new_table_schema_dialog: {
 | 
			
		||||
            title: 'স্কিমা নির্বাচন করুন',
 | 
			
		||||
            description:
 | 
			
		||||
                'বর্তমানে অনেক স্কিমা প্রদর্শিত হচ্ছে। নতুন টেবিলের জন্য একটি নির্বাচন করুন।',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            confirm: 'নিশ্চিত করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        update_table_schema_dialog: {
 | 
			
		||||
            title: 'স্কিমা পরিবর্তন করুন',
 | 
			
		||||
            description: 'টেবিল "{{tableName}}" এর জন্য স্কিমা আপডেট করুন',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            confirm: 'পরিবর্তন করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        star_us_dialog: {
 | 
			
		||||
            title: 'আমাদের উন্নত করতে সাহায্য করুন!',
 | 
			
		||||
            description:
 | 
			
		||||
                'আপনি কি GitHub-এ আমাদের একটি স্টার দিতে পারবেন? এটি মাত্র এক ক্লিক দূরে!',
 | 
			
		||||
            close: 'এখন নয়',
 | 
			
		||||
            confirm: 'অবশ্যই!',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_diagram_dialog: {
 | 
			
		||||
            title: 'চিত্র রপ্তানি করুন',
 | 
			
		||||
            description: 'রপ্তানির জন্য ফরম্যাট নির্বাচন করুন:',
 | 
			
		||||
            format_json: 'JSON',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            export: 'রপ্তানি করুন',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'চিত্র রপ্তানিতে ত্রুটি',
 | 
			
		||||
                description:
 | 
			
		||||
                    'কিছু ভুল হয়েছে। সাহায্যের প্রয়োজন? chartdb.io@gmail.com-এ যোগাযোগ করুন।',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        import_diagram_dialog: {
 | 
			
		||||
            title: 'চিত্র আমদানি করুন',
 | 
			
		||||
            description: 'নীচে ডায়াগ্রাম JSON পেস্ট করুন:',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            import: 'আমদানি করুন',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'চিত্র আমদানিতে ত্রুটি',
 | 
			
		||||
                description:
 | 
			
		||||
                    'ডায়াগ্রাম JSON অবৈধ। অনুগ্রহ করে JSON পরীক্ষা করুন এবং আবার চেষ্টা করুন। সাহায্যের প্রয়োজন? chartdb.io@gmail.com-এ যোগাযোগ করুন।',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        // TODO: Translate
 | 
			
		||||
        import_dbml_dialog: {
 | 
			
		||||
            example_title: 'Import Example DBML',
 | 
			
		||||
            title: 'Import DBML',
 | 
			
		||||
            description: 'Import a database schema from DBML format.',
 | 
			
		||||
            import: 'Import',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            skip_and_empty: 'Skip & Empty',
 | 
			
		||||
            show_example: 'Show Example',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Error',
 | 
			
		||||
                description: 'Failed to parse DBML. Please check the syntax.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        relationship_type: {
 | 
			
		||||
            one_to_one: 'এক থেকে এক',
 | 
			
		||||
            one_to_many: 'এক থেকে অনেক',
 | 
			
		||||
            many_to_one: 'অনেক থেকে এক',
 | 
			
		||||
            many_to_many: 'অনেক থেকে অনেক',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        canvas_context_menu: {
 | 
			
		||||
            new_table: 'নতুন টেবিল',
 | 
			
		||||
            new_relationship: 'নতুন সম্পর্ক',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        table_node_context_menu: {
 | 
			
		||||
            edit_table: 'টেবিল সম্পাদনা করুন',
 | 
			
		||||
            duplicate_table: 'টেবিল নকল করুন',
 | 
			
		||||
            delete_table: 'টেবিল মুছে ফেলুন',
 | 
			
		||||
            add_relationship: 'Add Relationship', // TODO: Translate
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        snap_to_grid_tooltip: 'গ্রিডে স্ন্যাপ করুন (অবস্থান {{key}})',
 | 
			
		||||
 | 
			
		||||
        tool_tips: {
 | 
			
		||||
            double_click_to_edit: 'সম্পাদনা করতে ডাবল-ক্লিক করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        language_select: {
 | 
			
		||||
            change_language: 'ভাষা পরিবর্তন করুন',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const bnMetadata: LanguageMetadata = {
 | 
			
		||||
    name: 'Bengali',
 | 
			
		||||
    nativeName: 'বাংলা',
 | 
			
		||||
    code: 'bn',
 | 
			
		||||
};
 | 
			
		||||
@@ -8,7 +8,7 @@ export const de: LanguageTranslation = {
 | 
			
		||||
                new: 'Neu',
 | 
			
		||||
                open: 'Öffnen',
 | 
			
		||||
                save: 'Speichern',
 | 
			
		||||
                import_database: 'Datenbank importieren',
 | 
			
		||||
                import: 'Datenbank importieren',
 | 
			
		||||
                export_sql: 'SQL exportieren',
 | 
			
		||||
                export_as: 'Exportieren als',
 | 
			
		||||
                delete_diagram: 'Diagramm löschen',
 | 
			
		||||
@@ -30,6 +30,9 @@ export const de: LanguageTranslation = {
 | 
			
		||||
                theme: 'Stil',
 | 
			
		||||
                show_dependencies: 'Abhängigkeiten anzeigen',
 | 
			
		||||
                hide_dependencies: 'Abhängigkeiten ausblenden',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_minimap: 'Show Mini Map',
 | 
			
		||||
                hide_minimap: 'Hide Mini Map',
 | 
			
		||||
            },
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            share: {
 | 
			
		||||
@@ -78,6 +81,18 @@ export const de: LanguageTranslation = {
 | 
			
		||||
            none: 'Keine',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        copy_to_clipboard_toast: {
 | 
			
		||||
            unsupported: {
 | 
			
		||||
                title: 'Kopieren fehlgeschlagen',
 | 
			
		||||
                description: 'Zwischenablage nicht unterstützt',
 | 
			
		||||
            },
 | 
			
		||||
            failed: {
 | 
			
		||||
                title: 'Kopieren fehlgeschlagen',
 | 
			
		||||
                description:
 | 
			
		||||
                    'Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        theme: {
 | 
			
		||||
            system: 'System',
 | 
			
		||||
            light: 'Hell',
 | 
			
		||||
@@ -91,7 +106,6 @@ export const de: LanguageTranslation = {
 | 
			
		||||
 | 
			
		||||
        last_saved: 'Zuletzt gespeichert',
 | 
			
		||||
        saved: 'Gespeichert',
 | 
			
		||||
        diagrams: 'Diagramme',
 | 
			
		||||
        loading_diagram: 'Diagramm wird geladen...',
 | 
			
		||||
        deselect_all: 'Alles abwählen',
 | 
			
		||||
        select_all: 'Alles auswählen',
 | 
			
		||||
@@ -112,6 +126,12 @@ export const de: LanguageTranslation = {
 | 
			
		||||
                add_table: 'Tabelle hinzufügen',
 | 
			
		||||
                filter: 'Filter',
 | 
			
		||||
                collapse: 'Alle einklappen',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                clear: 'Clear Filter',
 | 
			
		||||
                no_results: 'No tables found matching your filter.',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_list: 'Show Table List',
 | 
			
		||||
                show_dbml: 'Show DBML Editor',
 | 
			
		||||
 | 
			
		||||
                table: {
 | 
			
		||||
                    fields: 'Felder',
 | 
			
		||||
@@ -362,6 +382,20 @@ export const de: LanguageTranslation = {
 | 
			
		||||
                    'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        // TODO: Translate
 | 
			
		||||
        import_dbml_dialog: {
 | 
			
		||||
            example_title: 'Import Example DBML',
 | 
			
		||||
            title: 'Import DBML',
 | 
			
		||||
            description: 'Import a database schema from DBML format.',
 | 
			
		||||
            import: 'Import',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            skip_and_empty: 'Skip & Empty',
 | 
			
		||||
            show_example: 'Show Example',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Error',
 | 
			
		||||
                description: 'Failed to parse DBML. Please check the syntax.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        relationship_type: {
 | 
			
		||||
            one_to_one: 'Ein zu Eins (1:1)',
 | 
			
		||||
            one_to_many: 'Ein zu Viele (1:n)',
 | 
			
		||||
@@ -378,6 +412,7 @@ export const de: LanguageTranslation = {
 | 
			
		||||
            edit_table: 'Tabelle bearbeiten',
 | 
			
		||||
            duplicate_table: 'Duplicate Table', // TODO: Translate
 | 
			
		||||
            delete_table: 'Tabelle löschen',
 | 
			
		||||
            add_relationship: 'Add Relationship', // TODO: Translate
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // TODO: Add translations
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ export const en = {
 | 
			
		||||
                new: 'New',
 | 
			
		||||
                open: 'Open',
 | 
			
		||||
                save: 'Save',
 | 
			
		||||
                import_database: 'Import Database',
 | 
			
		||||
                import: 'Import',
 | 
			
		||||
                export_sql: 'Export SQL',
 | 
			
		||||
                export_as: 'Export as',
 | 
			
		||||
                delete_diagram: 'Delete Diagram',
 | 
			
		||||
@@ -30,6 +30,8 @@ export const en = {
 | 
			
		||||
                theme: 'Theme',
 | 
			
		||||
                show_dependencies: 'Show Dependencies',
 | 
			
		||||
                hide_dependencies: 'Hide Dependencies',
 | 
			
		||||
                show_minimap: 'Show Mini Map',
 | 
			
		||||
                hide_minimap: 'Hide Mini Map',
 | 
			
		||||
            },
 | 
			
		||||
            share: {
 | 
			
		||||
                share: 'Share',
 | 
			
		||||
@@ -77,6 +79,17 @@ export const en = {
 | 
			
		||||
            none: 'none',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        copy_to_clipboard_toast: {
 | 
			
		||||
            unsupported: {
 | 
			
		||||
                title: 'Copy failed',
 | 
			
		||||
                description: 'Clipboard not supported.',
 | 
			
		||||
            },
 | 
			
		||||
            failed: {
 | 
			
		||||
                title: 'Copy failed',
 | 
			
		||||
                description: 'Something went wrong. Please try again.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        theme: {
 | 
			
		||||
            system: 'System',
 | 
			
		||||
            light: 'Light',
 | 
			
		||||
@@ -90,7 +103,6 @@ export const en = {
 | 
			
		||||
 | 
			
		||||
        last_saved: 'Last saved',
 | 
			
		||||
        saved: 'Saved',
 | 
			
		||||
        diagrams: 'Diagrams',
 | 
			
		||||
        loading_diagram: 'Loading diagram...',
 | 
			
		||||
        deselect_all: 'Deselect All',
 | 
			
		||||
        select_all: 'Select All',
 | 
			
		||||
@@ -111,6 +123,10 @@ export const en = {
 | 
			
		||||
                add_table: 'Add Table',
 | 
			
		||||
                filter: 'Filter',
 | 
			
		||||
                collapse: 'Collapse All',
 | 
			
		||||
                clear: 'Clear Filter',
 | 
			
		||||
                no_results: 'No tables found matching your filter.',
 | 
			
		||||
                show_list: 'Show Table List',
 | 
			
		||||
                show_dbml: 'Show DBML Editor',
 | 
			
		||||
 | 
			
		||||
                table: {
 | 
			
		||||
                    fields: 'Fields',
 | 
			
		||||
@@ -349,7 +365,7 @@ export const en = {
 | 
			
		||||
 | 
			
		||||
        import_diagram_dialog: {
 | 
			
		||||
            title: 'Import Diagram',
 | 
			
		||||
            description: 'Paste the diagram JSON below:',
 | 
			
		||||
            description: 'Import a diagram from a JSON file.',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            import: 'Import',
 | 
			
		||||
            error: {
 | 
			
		||||
@@ -358,6 +374,20 @@ export const en = {
 | 
			
		||||
                    'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        import_dbml_dialog: {
 | 
			
		||||
            example_title: 'Import Example DBML',
 | 
			
		||||
            title: 'Import DBML',
 | 
			
		||||
            description: 'Import a database schema from DBML format.',
 | 
			
		||||
            import: 'Import',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            skip_and_empty: 'Skip & Empty',
 | 
			
		||||
            show_example: 'Show Example',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Error importing DBML',
 | 
			
		||||
                description: 'Failed to parse DBML. Please check the syntax.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        relationship_type: {
 | 
			
		||||
            one_to_one: 'One to One',
 | 
			
		||||
            one_to_many: 'One to Many',
 | 
			
		||||
@@ -374,6 +404,7 @@ export const en = {
 | 
			
		||||
            edit_table: 'Edit Table',
 | 
			
		||||
            duplicate_table: 'Duplicate Table',
 | 
			
		||||
            delete_table: 'Delete Table',
 | 
			
		||||
            add_relationship: 'Add Relationship',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ export const es: LanguageTranslation = {
 | 
			
		||||
                new: 'Nuevo',
 | 
			
		||||
                open: 'Abrir',
 | 
			
		||||
                save: 'Guardar',
 | 
			
		||||
                import_database: 'Importar Base de Datos',
 | 
			
		||||
                import: 'Importar Base de Datos',
 | 
			
		||||
                export_sql: 'Exportar SQL',
 | 
			
		||||
                export_as: 'Exportar como',
 | 
			
		||||
                delete_diagram: 'Eliminar Diagrama',
 | 
			
		||||
@@ -30,6 +30,9 @@ export const es: LanguageTranslation = {
 | 
			
		||||
                theme: 'Tema',
 | 
			
		||||
                show_dependencies: 'Mostrar dependencias',
 | 
			
		||||
                hide_dependencies: 'Ocultar dependencias',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_minimap: 'Show Mini Map',
 | 
			
		||||
                hide_minimap: 'Hide Mini Map',
 | 
			
		||||
            },
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            share: {
 | 
			
		||||
@@ -69,6 +72,17 @@ export const es: LanguageTranslation = {
 | 
			
		||||
            cancel: 'Cancelar',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        copy_to_clipboard_toast: {
 | 
			
		||||
            unsupported: {
 | 
			
		||||
                title: 'Copia fallida',
 | 
			
		||||
                description: 'Portapapeles no soportado',
 | 
			
		||||
            },
 | 
			
		||||
            failed: {
 | 
			
		||||
                title: 'Copia fallida',
 | 
			
		||||
                description: 'Algo salió mal. Por favor, inténtelo de nuevo.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        theme: {
 | 
			
		||||
            system: 'Sistema',
 | 
			
		||||
            light: 'Claro',
 | 
			
		||||
@@ -82,7 +96,6 @@ export const es: LanguageTranslation = {
 | 
			
		||||
 | 
			
		||||
        last_saved: 'Último guardado',
 | 
			
		||||
        saved: 'Guardado',
 | 
			
		||||
        diagrams: 'Diagramas',
 | 
			
		||||
        loading_diagram: 'Cargando diagrama...',
 | 
			
		||||
        deselect_all: 'Deseleccionar todo',
 | 
			
		||||
        select_all: 'Seleccionar todo',
 | 
			
		||||
@@ -103,6 +116,12 @@ export const es: LanguageTranslation = {
 | 
			
		||||
                add_table: 'Agregar Tabla',
 | 
			
		||||
                filter: 'Filtrar',
 | 
			
		||||
                collapse: 'Colapsar Todo',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                clear: 'Clear Filter',
 | 
			
		||||
                no_results: 'No tables found matching your filter.',
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                show_list: 'Show Table List',
 | 
			
		||||
                show_dbml: 'Show DBML Editor',
 | 
			
		||||
 | 
			
		||||
                table: {
 | 
			
		||||
                    fields: 'Campos',
 | 
			
		||||
@@ -362,6 +381,20 @@ export const es: LanguageTranslation = {
 | 
			
		||||
                    'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        // TODO: Translate
 | 
			
		||||
        import_dbml_dialog: {
 | 
			
		||||
            example_title: 'Import Example DBML',
 | 
			
		||||
            title: 'Import DBML',
 | 
			
		||||
            description: 'Import a database schema from DBML format.',
 | 
			
		||||
            import: 'Import',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            skip_and_empty: 'Skip & Empty',
 | 
			
		||||
            show_example: 'Show Example',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Error',
 | 
			
		||||
                description: 'Failed to parse DBML. Please check the syntax.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        relationship_type: {
 | 
			
		||||
            one_to_one: 'Uno a Uno',
 | 
			
		||||
            one_to_many: 'Uno a Muchos',
 | 
			
		||||
@@ -378,6 +411,7 @@ export const es: LanguageTranslation = {
 | 
			
		||||
            edit_table: 'Editar Tabla',
 | 
			
		||||
            duplicate_table: 'Duplicate Table', // TODO: Translate
 | 
			
		||||
            delete_table: 'Eliminar Tabla',
 | 
			
		||||
            add_relationship: 'Add Relationship', // TODO: Translate
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // TODO: Add translations
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
                new: 'Nouveau',
 | 
			
		||||
                open: 'Ouvrir',
 | 
			
		||||
                save: 'Enregistrer',
 | 
			
		||||
                import_database: 'Importer Base de Données',
 | 
			
		||||
                import: 'Importer Base de Données',
 | 
			
		||||
                export_sql: 'Exporter SQL',
 | 
			
		||||
                export_as: 'Exporter en tant que',
 | 
			
		||||
                delete_diagram: 'Supprimer le Diagramme',
 | 
			
		||||
@@ -30,6 +30,8 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
                theme: 'Thème',
 | 
			
		||||
                show_dependencies: 'Afficher les Dépendances',
 | 
			
		||||
                hide_dependencies: 'Masquer les Dépendances',
 | 
			
		||||
                show_minimap: 'Afficher la Mini Carte',
 | 
			
		||||
                hide_minimap: 'Masquer la Mini Carte',
 | 
			
		||||
            },
 | 
			
		||||
            share: {
 | 
			
		||||
                share: 'Partage',
 | 
			
		||||
@@ -68,6 +70,17 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
            cancel: 'Annuler',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        copy_to_clipboard_toast: {
 | 
			
		||||
            unsupported: {
 | 
			
		||||
                title: 'Échec de la copie',
 | 
			
		||||
                description: 'Presse-papiers non pris en charge',
 | 
			
		||||
            },
 | 
			
		||||
            failed: {
 | 
			
		||||
                title: 'Échec de la copie',
 | 
			
		||||
                description: 'Quelque chose a mal tourné. Veuillez réessayer.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        theme: {
 | 
			
		||||
            system: 'Système',
 | 
			
		||||
            light: 'Clair',
 | 
			
		||||
@@ -81,16 +94,14 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
 | 
			
		||||
        last_saved: 'Dernière sauvegarde',
 | 
			
		||||
        saved: 'Enregistré',
 | 
			
		||||
        diagrams: 'Diagrammes',
 | 
			
		||||
        loading_diagram: 'Chargement du diagramme...',
 | 
			
		||||
        deselect_all: 'Tout désélectionner',
 | 
			
		||||
        select_all: 'Tout sélectionner',
 | 
			
		||||
        clear: 'Effacer',
 | 
			
		||||
        show_more: 'Afficher Plus',
 | 
			
		||||
        show_less: 'Afficher Moins',
 | 
			
		||||
        // TODO: Translate
 | 
			
		||||
        copy_to_clipboard: 'Copy to Clipboard',
 | 
			
		||||
        copied: 'Copied!',
 | 
			
		||||
        copy_to_clipboard: 'Copier dans le presse-papiers',
 | 
			
		||||
        copied: 'Copié !',
 | 
			
		||||
 | 
			
		||||
        side_panel: {
 | 
			
		||||
            schema: 'Schéma:',
 | 
			
		||||
@@ -103,6 +114,11 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
                add_table: 'Ajouter une Table',
 | 
			
		||||
                filter: 'Filtrer',
 | 
			
		||||
                collapse: 'Réduire Tout',
 | 
			
		||||
                clear: 'Effacer le Filtre',
 | 
			
		||||
                no_results:
 | 
			
		||||
                    'Aucune table trouvée correspondant à votre filtre.',
 | 
			
		||||
                show_list: 'Afficher la Liste des Tableaux',
 | 
			
		||||
                show_dbml: "Afficher l'éditeur DBML",
 | 
			
		||||
 | 
			
		||||
                table: {
 | 
			
		||||
                    fields: 'Champs',
 | 
			
		||||
@@ -134,7 +150,7 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
                        title: 'Actions de la Table',
 | 
			
		||||
                        add_field: 'Ajouter un Champ',
 | 
			
		||||
                        add_index: 'Ajouter un Index',
 | 
			
		||||
                        duplicate_table: 'Duplicate Table', // TODO: Translate
 | 
			
		||||
                        duplicate_table: 'Tableau dupliqué',
 | 
			
		||||
                        delete_table: 'Supprimer la Table',
 | 
			
		||||
                        change_schema: 'Changer le Schéma',
 | 
			
		||||
                    },
 | 
			
		||||
@@ -217,14 +233,12 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
                    step_2: 'Si vous utilisez "Résultats en Grille", changez le nombre maximum de caractères récupérés pour les données non-XML (définir à 9999999).',
 | 
			
		||||
                },
 | 
			
		||||
                instructions_link: "Besoin d'aide ? Regardez comment",
 | 
			
		||||
                // TODO: Translate
 | 
			
		||||
                check_script_result: 'Check Script Result',
 | 
			
		||||
                check_script_result: 'Vérifier le résultat du Script',
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            cancel: 'Annuler',
 | 
			
		||||
            back: 'Retour',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            import_from_file: 'Import from File',
 | 
			
		||||
            import_from_file: "Importer à partir d'un fichier",
 | 
			
		||||
            empty_diagram: 'Diagramme vide',
 | 
			
		||||
            continue: 'Continuer',
 | 
			
		||||
            import: 'Importer',
 | 
			
		||||
@@ -339,29 +353,42 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
                cancel: 'Annuler',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        // TODO: Translate
 | 
			
		||||
        export_diagram_dialog: {
 | 
			
		||||
            title: 'Export Diagram',
 | 
			
		||||
            description: 'Choose the format for export:',
 | 
			
		||||
            title: 'Exporter le Diagramme',
 | 
			
		||||
            description: "Sélectionner le format d'exportation :",
 | 
			
		||||
            format_json: 'JSON',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            export: 'Export',
 | 
			
		||||
            cancel: 'Annuler',
 | 
			
		||||
            export: 'Exporter',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Error exporting diagram',
 | 
			
		||||
                title: "Erreur lors de l'exportation du diagramme",
 | 
			
		||||
                description:
 | 
			
		||||
                    'Something went wrong. Need help? chartdb.io@gmail.com',
 | 
			
		||||
                    "Une erreur s'est produite. Besoin d'aide ? chartdb.io@gmail.com",
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        // TODO: Translate
 | 
			
		||||
        import_diagram_dialog: {
 | 
			
		||||
            title: 'Import Diagram',
 | 
			
		||||
            description: 'Paste the diagram JSON below:',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            import: 'Import',
 | 
			
		||||
            title: 'Importer un diagramme',
 | 
			
		||||
            description: 'Coller le diagramme au format JSON ci-dessous :',
 | 
			
		||||
            cancel: 'Annuler',
 | 
			
		||||
            import: 'Exporter',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Error importing diagram',
 | 
			
		||||
                title: "Erreur lors de l'exportation du diagramme",
 | 
			
		||||
                description:
 | 
			
		||||
                    'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
 | 
			
		||||
                    "Le diagramme JSON n'est pas valide. Veuillez vérifier le JSON et réessayer. Besoin d'aide ? chartdb.io@gmail.com",
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        import_dbml_dialog: {
 | 
			
		||||
            example_title: "Exemple d'importation DBML",
 | 
			
		||||
            title: 'Import DBML',
 | 
			
		||||
            description:
 | 
			
		||||
                'Importer un schéma de base de données à partir du format DBML.',
 | 
			
		||||
            import: 'Importer',
 | 
			
		||||
            cancel: 'Annuler',
 | 
			
		||||
            skip_and_empty: 'Passer et vider',
 | 
			
		||||
            show_example: 'Afficher un exemple',
 | 
			
		||||
            error: {
 | 
			
		||||
                title: 'Erreur',
 | 
			
		||||
                description:
 | 
			
		||||
                    "Erreur d'analyse du DBML. Veuillez vérifier la syntaxe.",
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        relationship_type: {
 | 
			
		||||
@@ -378,12 +405,13 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
 | 
			
		||||
        table_node_context_menu: {
 | 
			
		||||
            edit_table: 'Éditer la Table',
 | 
			
		||||
            duplicate_table: 'Duplicate Table', // TODO: Translate
 | 
			
		||||
            duplicate_table: 'Tableau Dupliqué',
 | 
			
		||||
            delete_table: 'Supprimer la Table',
 | 
			
		||||
            add_relationship: 'Ajouter une Relation',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // TODO: Add translations
 | 
			
		||||
        snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
 | 
			
		||||
        snap_to_grid_tooltip:
 | 
			
		||||
            'Aligner sur la grille (maintenir la touche {{key}})',
 | 
			
		||||
 | 
			
		||||
        tool_tips: {
 | 
			
		||||
            double_click_to_edit: 'Double-cliquez pour modifier',
 | 
			
		||||
 
 | 
			
		||||