Compare commits

..

129 Commits

Author SHA1 Message Date
johnnyfish
4fdc2ccd91 feat: implement reusable click-outside hook for edit fields
- Create custom useClickOutside and useEditClickOutside hooks
- Replace useClickAway with custom hook for better event handling
- Add click-outside behavior to all edit fields:
  - Diagram name in navbar
  - Area names on canvas (with context menu)
  - Table names in side panel
  - Relationship names in side panel
  - Table edit mode panel
- Improve edit mode UX with auto-focus and text selection
- Add pencil icons for visual edit affordance
2025-09-15 19:22:23 +03:00
Guy Ben-Aharon
8954d893bb feat: add quick table mode on canvas (#915)
* fix: add quick table mode on canvas

* fix

* fix

* fix
2025-09-14 20:42:01 +03:00
Guy Ben-Aharon
1a6688e85e alignment (#912) 2025-09-11 12:32:58 +03:00
Guy Ben-Aharon
5e81c1848a fix(dbml): export array fields without quotes (#911) 2025-09-10 22:24:05 +03:00
Guy Ben-Aharon
2bd9ca25b2 fix: update deps vulns (#909) 2025-09-10 16:37:33 +03:00
Guy Ben-Aharon
b016a70691 fix: move auto arrange to toolbar (#904) 2025-09-07 12:02:33 +03:00
Jonathan Fishner
a0fb1ed08b feat: add zoom navigation buttons to canvas filter for tables and areas (#903)
* feat: add zoom navigation buttons to canvas filter for tables and areas

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-09-04 16:18:50 +03:00
Guy Ben-Aharon
ffddcdcc98 fix: export sql + import metadata lib (#902) 2025-09-04 12:10:56 +03:00
Jonathan Fishner
fe9ef275b8 fix: improve SQL default value parsing for PostgreSQL, MySQL, and SQL Server with proper type handling and casting support (#900) 2025-09-04 11:18:02 +03:00
Guy Ben-Aharon
df89f0b6b9 fix: remove general db creation (#901) 2025-09-03 20:57:12 +03:00
Guy Ben-Aharon
534d2858af readonly editor (#899) 2025-09-03 15:59:21 +03:00
Jonathan Fishner
2a64deebb8 fix(sql-import): handle SQL Server DDL with multiple tables, inline foreign keys, and case-insensitive field matching (#897) 2025-09-02 15:15:15 +03:00
Guy Ben-Aharon
e5e1d59327 fix: reset increment and default when change field (#896) 2025-09-01 18:48:00 +03:00
Guy Ben-Aharon
aa290615ca fix(sql-import): support ALTER TABLE ALTER COLUMN TYPE in PostgreSQL importer (#895) 2025-09-01 17:13:42 +03:00
Guy Ben-Aharon
ec6e46fe81 fix: add support for ALTER TABLE ADD COLUMN in PostgreSQL importer (#892) 2025-09-01 11:45:14 +03:00
Guy Ben-Aharon
ac128d67de align filter (#890) 2025-08-31 19:18:43 +03:00
Guy Ben-Aharon
07937a2f51 fix: export dbml issues after upgrade version (#883)
* fix: dbml export

* fix

* fix

* fix

* fix

* fix
2025-08-27 20:44:18 +03:00
Guy Ben-Aharon
d8e0bc7db8 fix: upgrade dbml lib (#880) 2025-08-27 14:42:02 +03:00
Guy Ben-Aharon
1ce265781b chore(main): release 1.15.1 (#878) 2025-08-27 12:53:00 +03:00
Aaron Dewes
60c5675cbf fix(custom-types): Make schema optional (#866)
* fix(custom-types): Make schema optional

The schema is optional in practice for custom types (as seen in the TS types above), and not always included in exports.

* add nullable

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-27 12:48:14 +03:00
Jonathan Fishner
66b086378c fix: handle quoted identifiers with special characters in SQL import/export and DBML generation (#877)
* fix: handle quoted identifiers with special characters in SQL import/export and DBML generation

* add tests and fix build

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-27 12:21:41 +03:00
Guy Ben-Aharon
abd2a6ccbe fix: add actions menu to diagram list + add duplicate diagram (#876) 2025-08-26 17:10:25 +03:00
Guy Ben-Aharon
459c5f1ce3 chore(main): release 1.15.0 (#835) 2025-08-26 15:15:41 +03:00
Guy Ben-Aharon
44be48ff3a fix: improve creating view to table dependency (#874) 2025-08-26 15:10:11 +03:00
Aaron Dewes
ad8e34483f fix(cla): Harden action (#867)
The CLA action does not need contents: write permission. Limit it to read for security.
2025-08-26 13:41:29 +03:00
Jonathan Fishner
215d57979d fix: preserve composite primary key constraint names across import/export workflows (#869)
* fix: composite primary key constraint names across import/export workflows

* fix: enhance PK index management with automatic lifecycle and improved UI

* fix build

* fix

* fix

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-26 12:48:24 +03:00
Guy Ben-Aharon
ec3719ebce fix: merge relationship & dependency sections to ref section (#870)
* fix: merge relationship & dependency sections to ref section

* fix

* fix

* fix
2025-08-25 20:14:32 +03:00
Guy Ben-Aharon
0a5874a69b feat: support create views (#868)
* feat: support create views

* fix

* fix

* fix

* fix

* fix
2025-08-25 16:14:28 +03:00
Guy Ben-Aharon
7e0fdd1595 fix: open filter by default (#863) 2025-08-21 17:55:45 +03:00
Guy Ben-Aharon
2531a7023f fix: move dbml into sections menu (#862) 2025-08-21 14:48:47 +03:00
Guy Ben-Aharon
73daf0df21 fix: area filter logic (#861) 2025-08-20 15:21:48 +03:00
Jonathan Fishner
c77c983989 feat: add auto increment support for fields with database-specific export (#851)
* feat: add auto increment support for fields with database-specific export

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-19 11:39:26 +03:00
Jonathan Fishner
0aaa451479 fix: prevent false change detection in DBML editor by stripping public schema on import (#858)
* fix: prevent false change detection in DBML editor by stripping public schema on import

* fix(dbml): preserve self-referencing relationships and character varying lengths in DBML import/export
2025-08-18 21:39:24 +03:00
Guy Ben-Aharon
b697e26170 fix(canvas): delete table + area together bug (#859) 2025-08-18 20:56:32 +03:00
Jonathan Fishner
04d91c67b1 fix(sql-import): fix SQL Server foreign key parsing for tables without schema prefix (#857) 2025-08-18 19:13:46 +03:00
Guy Ben-Aharon
d0dee84970 fix(filter): filter toggle issues with no schemas dbs (#856) 2025-08-17 12:54:56 +03:00
Guy Ben-Aharon
b4ccfcdcde fix: set default filter only if has more than 1 schemas (#855) 2025-08-14 11:37:24 +03:00
Guy Ben-Aharon
1759b0b9f2 fix: show default schema first (#854) 2025-08-13 21:08:53 +03:00
Guy Ben-Aharon
ab4845c772 fix: initially show filter when filter active (#853) 2025-08-13 20:20:59 +03:00
Jonathan Fishner
0545b41140 fix: DBML export error with multi-line table comments for SQL Server (#852)
* fix: DBML export error with multi-line table comments for SQL Server

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-13 18:15:38 +03:00
Guy Ben-Aharon
4520f8b1f7 update index.html (#850) 2025-08-13 11:34:08 +03:00
Guy Ben-Aharon
712bdf5b95 fix: filter to default schema on load new diagram (#849) 2025-08-12 18:07:19 +03:00
Jonathan Fishner
d7c9536272 fix: reorder with areas (#846) 2025-08-12 16:25:56 +03:00
Guy Ben-Aharon
815a52f192 update index.html (#848) 2025-08-12 14:31:41 +03:00
Guy Ben-Aharon
f1a4298362 fix: remove unnecessary space (#845) 2025-08-12 11:08:37 +03:00
Guy Ben-Aharon
b8f2141bd2 fix(sidebar): add titles to sidebar (#844)
* update shadcn

* menu v1

* menu v2

* resize menu items

* fix

* fix
2025-08-11 17:38:40 +03:00
Guy Ben-Aharon
eaebe34768 fix(menu): clear file menu (#843) 2025-08-11 11:46:33 +03:00
Jonathan Fishner
0d623a86b1 feat(postgres): add support hash index types (#812)
* feat(postgres): add support for hash index type with single column constraint

* some fixes

* some fixes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-10 20:24:34 +03:00
Guy Ben-Aharon
19fd94c6bd fix(area filter): fix dragging tables over filtered areas (#842) 2025-08-10 15:26:47 +03:00
Guy Ben-Aharon
0da3caeeac fix(table colors): switch to default table color (#841) 2025-08-10 14:42:22 +03:00
Guy Ben-Aharon
cb2ba66233 fix(select-box): fix select box issue in dialog (#840) 2025-08-10 14:06:09 +03:00
Guy Ben-Aharon
8a2267281b alignment of node converters (#839) 2025-08-10 13:36:55 +03:00
Guy Ben-Aharon
41ba251377 fix: update filter on adding table (#838) 2025-08-10 11:04:52 +03:00
Jonathan Fishner
e9c5442d9d feat(filter): filter tables by areas (#836)
* feat: auto-hide/show areas based on table visibility in canvas filter

* fix build

* fix

* fix

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-08-10 10:13:28 +03:00
Guy Ben-Aharon
4f1d3295c0 fix(filters): refactor diagram filters - remove schema filter (#832)
* refactor(filters): refactor diagram filters

* replace old filters

* fix storage

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix
2025-08-07 14:55:35 +03:00
Jonathan Fishner
5936500ca0 fix: for sqlite import - add more types & include type parameters (#834)
* fix: for sqlite import - add more types

* fix: preserve original data types in SQLite export instead of converting to storage classes

* fix: include type parameters (length, precision, scale) in SQLite export
2025-08-07 14:50:55 +03:00
Jonathan Fishner
43fc1d7fc2 feat: include foreign keys inline in SQLite CREATE TABLE statements (#833) 2025-08-07 11:55:15 +03:00
Guy Ben-Aharon
8dfa7cc62e chore(main): release 1.14.0 (#758) 2025-08-05 12:24:38 +03:00
Guy Ben-Aharon
23e93bfd01 fix: area resizers size (#830) 2025-08-04 20:56:19 +03:00
Guy Ben-Aharon
16f9f4671e fix(dbml): dbml indentation (#829) 2025-08-04 20:32:48 +03:00
Guy Ben-Aharon
0c300e5e72 fix(dbml): fix schemas with same table names (#828) 2025-08-04 17:50:28 +03:00
Guy Ben-Aharon
b9a1e78b53 fix(dbml): import dbml notes (table + fields) (#827) 2025-08-04 12:43:02 +03:00
Guy Ben-Aharon
337f7cdab4 fix(dbml): dbml note syntax (#826) 2025-08-04 12:21:37 +03:00
Guy Ben-Aharon
1b0390f0b7 feat(dbml): Edit Diagram Directly from DBML (#819)
* initial dbml apply

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix
2025-08-04 11:35:34 +03:00
Guy Ben-Aharon
bc52933b58 fix: update relationship when table width changes via expand/shrink (#825) 2025-08-03 22:41:11 +03:00
Guy Ben-Aharon
2fdad2344c solve issue with multiple render of tables (#824) 2025-08-03 22:13:18 +03:00
Guy Ben-Aharon
0c7eaa2df2 fix: solve issue with multiple render of tables (#823) 2025-08-03 21:52:17 +03:00
Guy Ben-Aharon
a5f8e56b3c fix(dbml): support multiple relationships on same field in inline DBML (#822) 2025-08-03 12:04:05 +03:00
Guy Ben-Aharon
8ffde62c1a fix(readonly): fix zoom out on readonly (#818) 2025-07-31 15:27:38 +03:00
Guy Ben-Aharon
39247b77a2 feat: enhance primary key and unique field handling logic (#817) 2025-07-31 11:38:33 +03:00
Guy Ben-Aharon
984b2aeee2 fix(ui): reduce spacing between primary key icon and short field types (#816) 2025-07-31 11:04:48 +03:00
Guy Ben-Aharon
eed104be5b fix(dbml): fix dbml output format (#815) 2025-07-30 14:31:56 +03:00
Guy Ben-Aharon
00bd535b3c fix(dbml import): fix dbml import types + schemas (#808)
* fix(dbml import): fix dbml import types + schemas

* fix(dbml import): fix dbml import types + schemas

* fix(dbml import): fix dbml import types + schemas

* fix
2025-07-29 17:55:29 +03:00
Guy Ben-Aharon
18e914242f fix(dbml export): fix handle tables with same name under different schemas (#807) 2025-07-29 16:22:09 +03:00
Guy Ben-Aharon
e68837a34a fix(dbml export): handle tables with same name under different schemas (#806) 2025-07-29 14:59:08 +03:00
Guy Ben-Aharon
b30162d98b fix: clone of custom types (#804) 2025-07-29 12:49:28 +03:00
Guy Ben-Aharon
dba372d25a fix(cockroachdb): support schema creation for cockroachdb (#803) 2025-07-28 18:55:05 +03:00
Jonathan Fishner
2eb48e75d3 fix(i18n): add Croatian (hr) language support (#802)
* feat: add Croatian (hr) language support

* fix translation

* fix: change langs order

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-28 18:12:17 +03:00
Guy Ben-Aharon
867903cd5f feat(schema): support create new schema (#801)
* feat(schema): support create new schema

* fix

* fix
2025-07-28 17:32:28 +03:00
Guy Ben-Aharon
8aeb1df0ad fix: fix screen freeze after schema select (#800)
* fix: fix screen freeze after schema select

* fix: fix screen freeze after schema select
2025-07-28 12:00:34 +03:00
Guy Ben-Aharon
6bea827293 fix(canvas filter): improve scroller on canvas filter (#799) 2025-07-28 11:48:45 +03:00
Guy Ben-Aharon
a119854da7 fix(dbml actions): set dbml tooltips side (#798) 2025-07-28 10:22:35 +03:00
Jonathan Fishner
bfbfd7b843 fix(dbml editor): move tooltips button to be on the right (#797) 2025-07-27 23:11:34 +03:00
Guy Ben-Aharon
0ca7008735 fix(dbml field comments): support export field comments in dbml (#796)
* fix(dbml field comments): support export field comments in dbml

* add tests
2025-07-27 20:53:55 +03:00
Guy Ben-Aharon
4bc71c52ff fix(scroll): disable scroll x behavior (#795) 2025-07-27 20:15:04 +03:00
Guy Ben-Aharon
8f27f10dec fix(dbml): support spaces in names (#794) 2025-07-27 19:44:43 +03:00
Guy Ben-Aharon
a93ec2cab9 fix: lost in canvas button animation (#793) 2025-07-27 17:34:48 +03:00
Jonathan Fishner
386e40a0bf fix: update MariaDB database import smart query (#792) 2025-07-27 16:29:16 +03:00
Jonathan Fishner
bda150d4b6 feat: add floating "Show All" button when tables are out of view (#787)
* feat: add floating "Show All" button when tables are out of view

* fix view of show all for mobile

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-27 16:25:14 +03:00
Guy Ben-Aharon
87836e53d1 fix: remove unnecessary import (#791) 2025-07-27 12:29:19 +03:00
Jonathan Fishner
7e0483f1a5 feat(custom-types): add highlight fields option for custom types (#726)
* feat(custom-types): add highlight feilds option for custom types

* fix(custom-types): show indicator when custom type is in used

* feat(canvas): add enum highlight indicator with pulse animation and double-click to clear

* some fixes

* some fixes

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-27 12:17:57 +03:00
Jonathan Fishner
309ee9cb0f fix(dbml-export): merge field attributes into single brackets and fix schema syntax (#790)
* fix(dbml-export): merge field attributes into single brackets and fix schema syntax

* fix build
2025-07-26 22:03:02 +03:00
Jonathan Fishner
79b885502e fix(sql-server): improvment for sql-server import via sql script (#789)
* feat: improvment for sql-server import via sql script

* fix for test

* some fixes

* some fixes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-25 19:16:35 +03:00
Guy Ben-Aharon
745bdee86d fix(table-node): fix comment icon on field (#786) 2025-07-24 17:27:23 +03:00
Guy Ben-Aharon
08eb9cc55f fix(table-node): improve field spacing (#785) 2025-07-24 16:41:45 +03:00
Jonathan Fishner
778f85d492 feat(datatypes): Add decimal / numeric attribute support + organize field row (#715)
* added decimal scale and precision support

* update i18n

* added button to reset - made values always enabled in pairs

* made button use ml

* added fix for when manually defined scales are set to 0

* fix

* some fixes

* some fixes

* some fixes

* some fixes

* some fixes

---------

Co-authored-by: Alexander Harris <mcalapurge@techie.com>
Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-24 15:18:33 +03:00
Guy Ben-Aharon
fb92be7d3e alignment sql export scripts (#784) 2025-07-23 21:00:52 +03:00
Jonathan Fishner
6df588f40e fix: improve SQL export formatting and add schema-aware FK grouping (#783)
* fix: correct foreign key direction based on relationship cardinality in SQL exports

* fix: improve SQL export formatting and add schema-aware FK grouping

* fix build

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-23 18:05:59 +03:00
Guy Ben-Aharon
b46ed58dff fix(table-select): add loading indication for import (#782) 2025-07-23 15:53:26 +03:00
Jonathan Fishner
0d9f57a9c9 feat: add table selection for large database imports (#776)
* feat: add table selection UI for large database imports (>50 tables)

* some changes

* some changes

* some changes

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-23 11:35:27 +03:00
Guy Ben-Aharon
b7dbe54c83 fix(canvas): fix filter eye button (#780) 2025-07-21 19:01:52 +03:00
Guy Ben-Aharon
43d1dfff71 fix: fix hotkeys on form elements (#778) 2025-07-21 17:31:42 +03:00
Guy Ben-Aharon
9949a46ee3 fix: set focus on filter search (#775) 2025-07-21 16:18:18 +03:00
Guy Ben-Aharon
dfbcf05b2f feat(canvas): Add filter tables on canvas (#774)
* feat(canvas): filter tables on canvas

* fix build

* fix

* fix
2025-07-21 15:54:27 +03:00
Jonathan Fishner
f56fab9876 fix: update multiple schemas toast to require user action (#771)
* fix: update multiple schemas toast to require user action

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-21 12:43:57 +03:00
Jonathan Fishner
c9ea7da092 feat(default value): add default value option to table field settings (#770)
* feat: add default value option to table field settings

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-21 12:29:53 +03:00
Jonathan Fishner
22d46e1e90 fix(dbml-import): handle unsupported DBML features and add comprehensive tests (#766)
* fix(dbml-import): handle unsupported DBML features and add comprehensive tests

* fix build

* fix(dbml-export): handle composite primary keys, invalid defaults, and char type formatting

* fix build

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-21 11:33:34 +03:00
Guy Ben-Aharon
6af94afc56 fix(area): redo/undo after dragging an area with tables (#767) 2025-07-17 16:49:05 +03:00
Jonathan Fishner
f7f92903de fix(sql-export): escape newlines and quotes in multi-line comments (#765) 2025-07-16 15:18:52 +03:00
Jonathan Fishner
b35e17526b feat: implement area grouping with parent-child relationships (#762)
* feat: implement area grouping with parent-child relationships

* fix: improve area node visual appearance and text visibility

* update area header color

* fix build

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-16 15:11:16 +03:00
Guy Ben-Aharon
bf32c08d37 fix: remove error lag after autofix (#764) 2025-07-14 21:27:05 +03:00
Jonathan Fishner
5d337409d6 fix: add PostgreSQL tests and fix parsing SQL (#760)
* feat: add PostgreSQL tests and fix parsing SQL

* fix: disable format on paste for SQL DDL import

* some ui fixes + add tests to the ci

* fix

* fix validator and importer

* fix for maria-db

* fix

* remove improved

* fix

* fix

* fix

* fix for test

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-14 19:25:44 +03:00
Guy Ben-Aharon
67f5ac303e fix: add open and create diagram to side menu (#757) 2025-07-08 13:28:44 +03:00
Guy Ben-Aharon
578546a171 chore(main): release 1.13.2 (#756) 2025-07-06 14:02:21 +03:00
Jonathan Fishner
aa0b629a3e fix: add DISABLE_ANALYTICS flag to opt-out of Fathom analytics (#750)
* feat: add DISABLE_ANALYTICS flag to opt-out of Fathom analytics

* fix: HIDE_BUCKLE_DOT_DEV -> HIDE_CHARTDB_CLOUD

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-07-06 13:52:58 +03:00
Guy Ben-Aharon
69beaa0a83 chore(main): release 1.13.1 (#735) 2025-07-05 18:14:47 +03:00
Guy Ben-Aharon
4fcc49d49a fix: general performance improvements on canvas (#751) 2025-07-04 12:23:29 +03:00
Guy Ben-Aharon
d15985e399 fix: resolve unresponsive cursor and input glitches when editing field comments (#749) 2025-07-02 20:33:52 +03:00
Jonathan Fishner
d429128e65 fix(dbml): Filter duplicate tables at diagram level before export dbml (#746) 2025-06-24 12:46:56 +03:00
Jonathan Fishner
2fce8326b6 fix(import-database): for custom types query to import supabase & timescale (#745) 2025-06-11 21:50:42 +03:00
Guy Ben-Aharon
433c68a33d lib refactor (#744) 2025-06-10 11:04:51 +03:00
Guy Ben-Aharon
58acb65f12 fix duplicate (#743)
* fix duplicate

* fix duplicate
2025-06-08 15:27:50 +03:00
Guy Ben-Aharon
7978955819 lib fix (#742) 2025-06-08 14:44:45 +03:00
Jonathan Fishner
c6118e0cdb fix(export-sql): conditionally show generic option and reorder by diagram type (#708)
* fix(export-sql): conditionally show generic option and reorder by diagram type

* fix

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
2025-06-08 12:32:05 +03:00
Jonathan Fishner
7d063b905f fix(import-db): fix mariadb import (#740) 2025-06-06 19:45:13 +03:00
Jonathan Fishner
e0ff198c3f fix(dbml-editor): for some cases that the dbml had issues (#739) 2025-06-06 19:41:51 +03:00
Vlad Kovaliov
8b86e1c229 fix(table name): updates table name value when its updated from canvas/sidebar (#716) 2025-06-05 15:36:59 +03:00
Guy Ben-Aharon
24be28a662 fix(custom_types): fix display custom types in select box (#737) 2025-06-05 15:21:55 +03:00
Guy Ben-Aharon
c6788b4917 fix(performance): improve storage provider performance (#734)
* fix(performance): improve storage provider performance

* fix
2025-06-04 11:22:33 +03:00
313 changed files with 126127 additions and 9771 deletions

View File

@@ -25,3 +25,6 @@ jobs:
- name: Build - name: Build
run: npm run build run: npm run build
- name: Run tests
run: npm run test:ci

View File

@@ -7,7 +7,7 @@ on:
permissions: permissions:
actions: write actions: write
contents: write # this can be 'read' if the signatures are in remote repository contents: read
pull-requests: write pull-requests: write
statuses: write statuses: write

2
.nvmrc
View File

@@ -1 +1 @@
v22.5.1 v22.18.0

View File

@@ -1,5 +1,141 @@
# Changelog # Changelog
## [1.15.1](https://github.com/chartdb/chartdb/compare/v1.15.0...v1.15.1) (2025-08-27)
### Bug Fixes
* add actions menu to diagram list + add duplicate diagram ([#876](https://github.com/chartdb/chartdb/issues/876)) ([abd2a6c](https://github.com/chartdb/chartdb/commit/abd2a6ccbe1aa63db44ec28b3eff525cc5d3f8b0))
* **custom-types:** Make schema optional ([#866](https://github.com/chartdb/chartdb/issues/866)) ([60c5675](https://github.com/chartdb/chartdb/commit/60c5675cbfe205859d2d0c9848d8345a0a854671))
* handle quoted identifiers with special characters in SQL import/export and DBML generation ([#877](https://github.com/chartdb/chartdb/issues/877)) ([66b0863](https://github.com/chartdb/chartdb/commit/66b086378cd63347acab5fc7f13db7db4feaa872))
## [1.15.0](https://github.com/chartdb/chartdb/compare/v1.14.0...v1.15.0) (2025-08-26)
### Features
* add auto increment support for fields with database-specific export ([#851](https://github.com/chartdb/chartdb/issues/851)) ([c77c983](https://github.com/chartdb/chartdb/commit/c77c983989ae38a6b1139dd9015f4f3178d4e103))
* **filter:** filter tables by areas ([#836](https://github.com/chartdb/chartdb/issues/836)) ([e9c5442](https://github.com/chartdb/chartdb/commit/e9c5442d9df2beadad78187da3363bb6406636c4))
* include foreign keys inline in SQLite CREATE TABLE statements ([#833](https://github.com/chartdb/chartdb/issues/833)) ([43fc1d7](https://github.com/chartdb/chartdb/commit/43fc1d7fc26876b22c61405f6c3df89fc66b7992))
* **postgres:** add support hash index types ([#812](https://github.com/chartdb/chartdb/issues/812)) ([0d623a8](https://github.com/chartdb/chartdb/commit/0d623a86b1cb7cbd223e10ad23d09fc0e106c006))
* support create views ([#868](https://github.com/chartdb/chartdb/issues/868)) ([0a5874a](https://github.com/chartdb/chartdb/commit/0a5874a69b6323145430c1fb4e3482ac7da4916c))
### Bug Fixes
* area filter logic ([#861](https://github.com/chartdb/chartdb/issues/861)) ([73daf0d](https://github.com/chartdb/chartdb/commit/73daf0df2142a29c2eeebe60b43198bcca869026))
* **area filter:** fix dragging tables over filtered areas ([#842](https://github.com/chartdb/chartdb/issues/842)) ([19fd94c](https://github.com/chartdb/chartdb/commit/19fd94c6bde3a9ec749cd1ccacbedb6abc96d037))
* **canvas:** delete table + area together bug ([#859](https://github.com/chartdb/chartdb/issues/859)) ([b697e26](https://github.com/chartdb/chartdb/commit/b697e26170da95dcb427ff6907b6f663c98ba59f))
* **cla:** Harden action ([#867](https://github.com/chartdb/chartdb/issues/867)) ([ad8e344](https://github.com/chartdb/chartdb/commit/ad8e34483fdf4226de76c9e7768bc2ba9bf154de))
* DBML export error with multi-line table comments for SQL Server ([#852](https://github.com/chartdb/chartdb/issues/852)) ([0545b41](https://github.com/chartdb/chartdb/commit/0545b411407b2449220d10981a04c3e368a90ca3))
* filter to default schema on load new diagram ([#849](https://github.com/chartdb/chartdb/issues/849)) ([712bdf5](https://github.com/chartdb/chartdb/commit/712bdf5b958919d940c4f2a1c3b7c7e969990f02))
* **filter:** filter toggle issues with no schemas dbs ([#856](https://github.com/chartdb/chartdb/issues/856)) ([d0dee84](https://github.com/chartdb/chartdb/commit/d0dee849702161d979b4f589a7e6579fbaade22d))
* **filters:** refactor diagram filters - remove schema filter ([#832](https://github.com/chartdb/chartdb/issues/832)) ([4f1d329](https://github.com/chartdb/chartdb/commit/4f1d3295c09782ab46d82ce21b662032aa094f22))
* for sqlite import - add more types & include type parameters ([#834](https://github.com/chartdb/chartdb/issues/834)) ([5936500](https://github.com/chartdb/chartdb/commit/5936500ca00a57b3f161616264c26152a13c36d2))
* improve creating view to table dependency ([#874](https://github.com/chartdb/chartdb/issues/874)) ([44be48f](https://github.com/chartdb/chartdb/commit/44be48ff3ad1361279331c17364090b13af471a1))
* initially show filter when filter active ([#853](https://github.com/chartdb/chartdb/issues/853)) ([ab4845c](https://github.com/chartdb/chartdb/commit/ab4845c7728e6e0b2d852f8005921fd90630eef9))
* **menu:** clear file menu ([#843](https://github.com/chartdb/chartdb/issues/843)) ([eaebe34](https://github.com/chartdb/chartdb/commit/eaebe3476824af779214a354b3e991923a22f195))
* merge relationship & dependency sections to ref section ([#870](https://github.com/chartdb/chartdb/issues/870)) ([ec3719e](https://github.com/chartdb/chartdb/commit/ec3719ebce4664b2aa6e3322fb3337e72bc21015))
* move dbml into sections menu ([#862](https://github.com/chartdb/chartdb/issues/862)) ([2531a70](https://github.com/chartdb/chartdb/commit/2531a7023f36ef29e67c0da6bca4fd0346b18a51))
* open filter by default ([#863](https://github.com/chartdb/chartdb/issues/863)) ([7e0fdd1](https://github.com/chartdb/chartdb/commit/7e0fdd1595bffe29e769d29602d04f42edfe417e))
* preserve composite primary key constraint names across import/export workflows ([#869](https://github.com/chartdb/chartdb/issues/869)) ([215d579](https://github.com/chartdb/chartdb/commit/215d57979df2e91fa61988acff590daad2f4e771))
* prevent false change detection in DBML editor by stripping public schema on import ([#858](https://github.com/chartdb/chartdb/issues/858)) ([0aaa451](https://github.com/chartdb/chartdb/commit/0aaa451479911d047e4cc83f063afa68a122ba9b))
* remove unnecessary space ([#845](https://github.com/chartdb/chartdb/issues/845)) ([f1a4298](https://github.com/chartdb/chartdb/commit/f1a429836221aacdda73b91665bf33ffb011164c))
* reorder with areas ([#846](https://github.com/chartdb/chartdb/issues/846)) ([d7c9536](https://github.com/chartdb/chartdb/commit/d7c9536272cf1d42104b7064ea448d128d091a20))
* **select-box:** fix select box issue in dialog ([#840](https://github.com/chartdb/chartdb/issues/840)) ([cb2ba66](https://github.com/chartdb/chartdb/commit/cb2ba66233c8c04e2d963cf2d210499d8512a268))
* set default filter only if has more than 1 schemas ([#855](https://github.com/chartdb/chartdb/issues/855)) ([b4ccfcd](https://github.com/chartdb/chartdb/commit/b4ccfcdcde2f3565b0d3bbc46fa1715feb6cd925))
* show default schema first ([#854](https://github.com/chartdb/chartdb/issues/854)) ([1759b0b](https://github.com/chartdb/chartdb/commit/1759b0b9f271ed25f7c71f26c344e3f1d97bc5fb))
* **sidebar:** add titles to sidebar ([#844](https://github.com/chartdb/chartdb/issues/844)) ([b8f2141](https://github.com/chartdb/chartdb/commit/b8f2141bd2e67272030896fb4009a7925f9f09e4))
* **sql-import:** fix SQL Server foreign key parsing for tables without schema prefix ([#857](https://github.com/chartdb/chartdb/issues/857)) ([04d91c6](https://github.com/chartdb/chartdb/commit/04d91c67b1075e94948f75186878e633df7abbca))
* **table colors:** switch to default table color ([#841](https://github.com/chartdb/chartdb/issues/841)) ([0da3cae](https://github.com/chartdb/chartdb/commit/0da3caeeac37926dd22f38d98423611f39c0412a))
* update filter on adding table ([#838](https://github.com/chartdb/chartdb/issues/838)) ([41ba251](https://github.com/chartdb/chartdb/commit/41ba25137789dda25266178cd7c96ecbb37e62a4))
## [1.14.0](https://github.com/chartdb/chartdb/compare/v1.13.2...v1.14.0) (2025-08-04)
### Features
* add floating "Show All" button when tables are out of view ([#787](https://github.com/chartdb/chartdb/issues/787)) ([bda150d](https://github.com/chartdb/chartdb/commit/bda150d4b6d6fb90beb423efba69349d21a037a5))
* add table selection for large database imports ([#776](https://github.com/chartdb/chartdb/issues/776)) ([0d9f57a](https://github.com/chartdb/chartdb/commit/0d9f57a9c969a67e350d6bf25e07c3a9ef5bba39))
* **canvas:** Add filter tables on canvas ([#774](https://github.com/chartdb/chartdb/issues/774)) ([dfbcf05](https://github.com/chartdb/chartdb/commit/dfbcf05b2f595f5b7b77dd61abf77e6e07acaf8f))
* **custom-types:** add highlight fields option for custom types ([#726](https://github.com/chartdb/chartdb/issues/726)) ([7e0483f](https://github.com/chartdb/chartdb/commit/7e0483f1a5512a6a737baf61caf7513e043f2e96))
* **datatypes:** Add decimal / numeric attribute support + organize field row ([#715](https://github.com/chartdb/chartdb/issues/715)) ([778f85d](https://github.com/chartdb/chartdb/commit/778f85d49214232a39710e47bb5d4ec41b75d427))
* **dbml:** Edit Diagram Directly from DBML ([#819](https://github.com/chartdb/chartdb/issues/819)) ([1b0390f](https://github.com/chartdb/chartdb/commit/1b0390f0b7652fe415540b7942cf53ec87143f08))
* **default value:** add default value option to table field settings ([#770](https://github.com/chartdb/chartdb/issues/770)) ([c9ea7da](https://github.com/chartdb/chartdb/commit/c9ea7da0923ff991cb936235674d9a52b8186137))
* enhance primary key and unique field handling logic ([#817](https://github.com/chartdb/chartdb/issues/817)) ([39247b7](https://github.com/chartdb/chartdb/commit/39247b77a299caa4f29ea434af3028155c6d37ed))
* implement area grouping with parent-child relationships ([#762](https://github.com/chartdb/chartdb/issues/762)) ([b35e175](https://github.com/chartdb/chartdb/commit/b35e17526b3c9b918928ae5f3f89711ea7b2529c))
* **schema:** support create new schema ([#801](https://github.com/chartdb/chartdb/issues/801)) ([867903c](https://github.com/chartdb/chartdb/commit/867903cd5f24d96ce1fe718dc9b562e2f2b75276))
### Bug Fixes
* add open and create diagram to side menu ([#757](https://github.com/chartdb/chartdb/issues/757)) ([67f5ac3](https://github.com/chartdb/chartdb/commit/67f5ac303ebf5ada97d5c80fb08a2815ca205a91))
* add PostgreSQL tests and fix parsing SQL ([#760](https://github.com/chartdb/chartdb/issues/760)) ([5d33740](https://github.com/chartdb/chartdb/commit/5d337409d64d1078b538350016982a98e684c06c))
* area resizers size ([#830](https://github.com/chartdb/chartdb/issues/830)) ([23e93bf](https://github.com/chartdb/chartdb/commit/23e93bfd01d741dd3d11aa5c479cef97e1a86fa6))
* **area:** redo/undo after dragging an area with tables ([#767](https://github.com/chartdb/chartdb/issues/767)) ([6af94af](https://github.com/chartdb/chartdb/commit/6af94afc56cf8987b8fc9e3f0a9bfa966de35408))
* **canvas filter:** improve scroller on canvas filter ([#799](https://github.com/chartdb/chartdb/issues/799)) ([6bea827](https://github.com/chartdb/chartdb/commit/6bea82729362a8c7b73dc089ddd9e52bae176aa2))
* **canvas:** fix filter eye button ([#780](https://github.com/chartdb/chartdb/issues/780)) ([b7dbe54](https://github.com/chartdb/chartdb/commit/b7dbe54c83c75cfe3c556f7a162055dcfe2de23d))
* clone of custom types ([#804](https://github.com/chartdb/chartdb/issues/804)) ([b30162d](https://github.com/chartdb/chartdb/commit/b30162d98bc659a61aae023cdeaead4ce25c7ae9))
* **cockroachdb:** support schema creation for cockroachdb ([#803](https://github.com/chartdb/chartdb/issues/803)) ([dba372d](https://github.com/chartdb/chartdb/commit/dba372d25a8c642baf8600d05aa154882729d446))
* **dbml actions:** set dbml tooltips side ([#798](https://github.com/chartdb/chartdb/issues/798)) ([a119854](https://github.com/chartdb/chartdb/commit/a119854da7c935eb595984ea9398e04136ce60c4))
* **dbml editor:** move tooltips button to be on the right ([#797](https://github.com/chartdb/chartdb/issues/797)) ([bfbfd7b](https://github.com/chartdb/chartdb/commit/bfbfd7b843f96c894b1966ad95393b866c927466))
* **dbml export:** fix handle tables with same name under different schemas ([#807](https://github.com/chartdb/chartdb/issues/807)) ([18e9142](https://github.com/chartdb/chartdb/commit/18e914242faccd6376fe5a7cd5a4478667f065ee))
* **dbml export:** handle tables with same name under different schemas ([#806](https://github.com/chartdb/chartdb/issues/806)) ([e68837a](https://github.com/chartdb/chartdb/commit/e68837a34aa635fb6fc02c7f1289495e5c448242))
* **dbml field comments:** support export field comments in dbml ([#796](https://github.com/chartdb/chartdb/issues/796)) ([0ca7008](https://github.com/chartdb/chartdb/commit/0ca700873577bbfbf1dd3f8088c258fc89b10c53))
* **dbml import:** fix dbml import types + schemas ([#808](https://github.com/chartdb/chartdb/issues/808)) ([00bd535](https://github.com/chartdb/chartdb/commit/00bd535b3c62d26d25a6276d52beb10e26afad76))
* **dbml-export:** merge field attributes into single brackets and fix schema syntax ([#790](https://github.com/chartdb/chartdb/issues/790)) ([309ee9c](https://github.com/chartdb/chartdb/commit/309ee9cb0ff1f5a68ed183e3919e1a11a8410909))
* **dbml-import:** handle unsupported DBML features and add comprehensive tests ([#766](https://github.com/chartdb/chartdb/issues/766)) ([22d46e1](https://github.com/chartdb/chartdb/commit/22d46e1e90729730cc25dd6961bfe8c3d2ae0c98))
* **dbml:** dbml indentation ([#829](https://github.com/chartdb/chartdb/issues/829)) ([16f9f46](https://github.com/chartdb/chartdb/commit/16f9f4671e011eb66ba9594bed47570eda3eed66))
* **dbml:** dbml note syntax ([#826](https://github.com/chartdb/chartdb/issues/826)) ([337f7cd](https://github.com/chartdb/chartdb/commit/337f7cdab4759d15cb4d25a8c0e9394e99ba33d4))
* **dbml:** fix dbml output format ([#815](https://github.com/chartdb/chartdb/issues/815)) ([eed104b](https://github.com/chartdb/chartdb/commit/eed104be5ba2b7d9940ffac38e7877722ad764fc))
* **dbml:** fix schemas with same table names ([#828](https://github.com/chartdb/chartdb/issues/828)) ([0c300e5](https://github.com/chartdb/chartdb/commit/0c300e5e72cc5ff22cac42f8dbaed167061157c6))
* **dbml:** import dbml notes (table + fields) ([#827](https://github.com/chartdb/chartdb/issues/827)) ([b9a1e78](https://github.com/chartdb/chartdb/commit/b9a1e78b53c932c0b1a12ee38b62494a5c2f9348))
* **dbml:** support multiple relationships on same field in inline DBML ([#822](https://github.com/chartdb/chartdb/issues/822)) ([a5f8e56](https://github.com/chartdb/chartdb/commit/a5f8e56b3ca97b851b6953481644d3a3ff7ce882))
* **dbml:** support spaces in names ([#794](https://github.com/chartdb/chartdb/issues/794)) ([8f27f10](https://github.com/chartdb/chartdb/commit/8f27f10dec96af400dc2c12a30b22b3a346803a9))
* fix hotkeys on form elements ([#778](https://github.com/chartdb/chartdb/issues/778)) ([43d1dff](https://github.com/chartdb/chartdb/commit/43d1dfff71f2b960358a79b0112b78d11df91fb7))
* fix screen freeze after schema select ([#800](https://github.com/chartdb/chartdb/issues/800)) ([8aeb1df](https://github.com/chartdb/chartdb/commit/8aeb1df0ad353c49e91243453f24bfa5921a89ab))
* **i18n:** add Croatian (hr) language support ([#802](https://github.com/chartdb/chartdb/issues/802)) ([2eb48e7](https://github.com/chartdb/chartdb/commit/2eb48e75d303d622f51327d22502a6f78e7fb32d))
* improve SQL export formatting and add schema-aware FK grouping ([#783](https://github.com/chartdb/chartdb/issues/783)) ([6df588f](https://github.com/chartdb/chartdb/commit/6df588f40e6e7066da6125413b94466429d48767))
* lost in canvas button animation ([#793](https://github.com/chartdb/chartdb/issues/793)) ([a93ec2c](https://github.com/chartdb/chartdb/commit/a93ec2cab906d0e4431d8d1668adcf2dbfc3c80f))
* **readonly:** fix zoom out on readonly ([#818](https://github.com/chartdb/chartdb/issues/818)) ([8ffde62](https://github.com/chartdb/chartdb/commit/8ffde62c1a00893c4bf6b4dd39068df530375416))
* remove error lag after autofix ([#764](https://github.com/chartdb/chartdb/issues/764)) ([bf32c08](https://github.com/chartdb/chartdb/commit/bf32c08d37c02ee6d7946a41633bb97b2271fcb7))
* remove unnecessary import ([#791](https://github.com/chartdb/chartdb/issues/791)) ([87836e5](https://github.com/chartdb/chartdb/commit/87836e53d145b825f9c4f80abca72f418df50e6c))
* **scroll:** disable scroll x behavior ([#795](https://github.com/chartdb/chartdb/issues/795)) ([4bc71c5](https://github.com/chartdb/chartdb/commit/4bc71c52ff5c462800d8530b72a5aadb7d7f85ed))
* set focus on filter search ([#775](https://github.com/chartdb/chartdb/issues/775)) ([9949a46](https://github.com/chartdb/chartdb/commit/9949a46ee3ba7f46a2ea7f2c0d7101cc9336df4f))
* solve issue with multiple render of tables ([#823](https://github.com/chartdb/chartdb/issues/823)) ([0c7eaa2](https://github.com/chartdb/chartdb/commit/0c7eaa2df20cfb6994b7e6251c760a2d4581c879))
* **sql-export:** escape newlines and quotes in multi-line comments ([#765](https://github.com/chartdb/chartdb/issues/765)) ([f7f9290](https://github.com/chartdb/chartdb/commit/f7f92903def84a94ac0c66f625f96a6681383945))
* **sql-server:** improvment for sql-server import via sql script ([#789](https://github.com/chartdb/chartdb/issues/789)) ([79b8855](https://github.com/chartdb/chartdb/commit/79b885502e3385e996a52093a3ccd5f6e469993a))
* **table-node:** fix comment icon on field ([#786](https://github.com/chartdb/chartdb/issues/786)) ([745bdee](https://github.com/chartdb/chartdb/commit/745bdee86d07f1e9c3a2d24237c48c25b9a8eeea))
* **table-node:** improve field spacing ([#785](https://github.com/chartdb/chartdb/issues/785)) ([08eb9cc](https://github.com/chartdb/chartdb/commit/08eb9cc55f0077f53afea6f9ce720341e1a583c2))
* **table-select:** add loading indication for import ([#782](https://github.com/chartdb/chartdb/issues/782)) ([b46ed58](https://github.com/chartdb/chartdb/commit/b46ed58dff1ec74579fb1544dba46b0f77730c52))
* **ui:** reduce spacing between primary key icon and short field types ([#816](https://github.com/chartdb/chartdb/issues/816)) ([984b2ae](https://github.com/chartdb/chartdb/commit/984b2aeee22c43cb9bda77df2c22087973079af4))
* update MariaDB database import smart query ([#792](https://github.com/chartdb/chartdb/issues/792)) ([386e40a](https://github.com/chartdb/chartdb/commit/386e40a0bf93d9aef1486bb1e729d8f485e675eb))
* update multiple schemas toast to require user action ([#771](https://github.com/chartdb/chartdb/issues/771)) ([f56fab9](https://github.com/chartdb/chartdb/commit/f56fab9876fb9fc46c6c708231324a90d8a7851d))
* update relationship when table width changes via expand/shrink ([#825](https://github.com/chartdb/chartdb/issues/825)) ([bc52933](https://github.com/chartdb/chartdb/commit/bc52933b58bfe6bc73779d9401128254cbf497d5))
## [1.13.2](https://github.com/chartdb/chartdb/compare/v1.13.1...v1.13.2) (2025-07-06)
### Bug Fixes
* add DISABLE_ANALYTICS flag to opt-out of Fathom analytics ([#750](https://github.com/chartdb/chartdb/issues/750)) ([aa0b629](https://github.com/chartdb/chartdb/commit/aa0b629a3eaf8e8b60473ea3f28f769270c7714c))
## [1.13.1](https://github.com/chartdb/chartdb/compare/v1.13.0...v1.13.1) (2025-07-04)
### Bug Fixes
* **custom_types:** fix display custom types in select box ([#737](https://github.com/chartdb/chartdb/issues/737)) ([24be28a](https://github.com/chartdb/chartdb/commit/24be28a662c48fc5bc62e76446b9669d83d7d3e0))
* **dbml-editor:** for some cases that the dbml had issues ([#739](https://github.com/chartdb/chartdb/issues/739)) ([e0ff198](https://github.com/chartdb/chartdb/commit/e0ff198c3fd416498dac5680bb323ec88c54b65c))
* **dbml:** Filter duplicate tables at diagram level before export dbml ([#746](https://github.com/chartdb/chartdb/issues/746)) ([d429128](https://github.com/chartdb/chartdb/commit/d429128e65aa28c500eac2487356e4869506e948))
* **export-sql:** conditionally show generic option and reorder by diagram type ([#708](https://github.com/chartdb/chartdb/issues/708)) ([c6118e0](https://github.com/chartdb/chartdb/commit/c6118e0cdb0e5caaf73447d33db2fde1a98efe60))
* general performance improvements on canvas ([#751](https://github.com/chartdb/chartdb/issues/751)) ([4fcc49d](https://github.com/chartdb/chartdb/commit/4fcc49d49a76a4b886ffd6cf0b40cf2fc49952ec))
* **import-database:** for custom types query to import supabase & timescale ([#745](https://github.com/chartdb/chartdb/issues/745)) ([2fce832](https://github.com/chartdb/chartdb/commit/2fce8326b67b751d38dd34f409fea574449d0298))
* **import-db:** fix mariadb import ([#740](https://github.com/chartdb/chartdb/issues/740)) ([7d063b9](https://github.com/chartdb/chartdb/commit/7d063b905f19f51501468bd0bd794a25cf65e1be))
* **performance:** improve storage provider performance ([#734](https://github.com/chartdb/chartdb/issues/734)) ([c6788b4](https://github.com/chartdb/chartdb/commit/c6788b49173d9cce23571daeb460285cb7cffb11))
* resolve unresponsive cursor and input glitches when editing field comments ([#749](https://github.com/chartdb/chartdb/issues/749)) ([d15985e](https://github.com/chartdb/chartdb/commit/d15985e3999a0cd54213b2fb08c55d48a1b8b3b2))
* **table name:** updates table name value when its updated from canvas/sidebar ([#716](https://github.com/chartdb/chartdb/issues/716)) ([8b86e1c](https://github.com/chartdb/chartdb/commit/8b86e1c22992aaadcce7ad5fc1d267c5a57a99f0))
## [1.13.0](https://github.com/chartdb/chartdb/compare/v1.12.0...v1.13.0) (2025-05-28) ## [1.13.0](https://github.com/chartdb/chartdb/compare/v1.12.0...v1.13.0) (2025-05-28)

View File

@@ -3,7 +3,8 @@ FROM node:22-alpine AS builder
ARG VITE_OPENAI_API_KEY ARG VITE_OPENAI_API_KEY
ARG VITE_OPENAI_API_ENDPOINT ARG VITE_OPENAI_API_ENDPOINT
ARG VITE_LLM_MODEL_NAME ARG VITE_LLM_MODEL_NAME
ARG VITE_HIDE_BUCKLE_DOT_DEV ARG VITE_HIDE_CHARTDB_CLOUD
ARG VITE_DISABLE_ANALYTICS
WORKDIR /usr/src/app WORKDIR /usr/src/app
@@ -16,7 +17,8 @@ COPY . .
RUN echo "VITE_OPENAI_API_KEY=${VITE_OPENAI_API_KEY}" > .env && \ RUN echo "VITE_OPENAI_API_KEY=${VITE_OPENAI_API_KEY}" > .env && \
echo "VITE_OPENAI_API_ENDPOINT=${VITE_OPENAI_API_ENDPOINT}" >> .env && \ echo "VITE_OPENAI_API_ENDPOINT=${VITE_OPENAI_API_ENDPOINT}" >> .env && \
echo "VITE_LLM_MODEL_NAME=${VITE_LLM_MODEL_NAME}" >> .env && \ echo "VITE_LLM_MODEL_NAME=${VITE_LLM_MODEL_NAME}" >> .env && \
echo "VITE_HIDE_BUCKLE_DOT_DEV=${VITE_HIDE_BUCKLE_DOT_DEV}" >> .env echo "VITE_HIDE_CHARTDB_CLOUD=${VITE_HIDE_CHARTDB_CLOUD}" >> .env && \
echo "VITE_DISABLE_ANALYTICS=${VITE_DISABLE_ANALYTICS}" >> .env
RUN npm run build RUN npm run build

View File

@@ -125,6 +125,8 @@ docker run \
-p 8080:80 chartdb -p 8080:80 chartdb
``` ```
> **Privacy Note:** ChartDB includes privacy-focused analytics via Fathom Analytics. You can disable this by adding `-e DISABLE_ANALYTICS=true` to the run command or `--build-arg VITE_DISABLE_ANALYTICS=true` when building.
> **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. > **Note:** You must configure either Option 1 (OpenAI API key) OR Option 2 (Custom endpoint and model name) for AI capabilities to work. Do not mix the two options.
Open your browser and navigate to `http://localhost:8080`. Open your browser and navigate to `http://localhost:8080`.

View File

@@ -14,7 +14,8 @@ server {
OPENAI_API_KEY: \"$OPENAI_API_KEY\", OPENAI_API_KEY: \"$OPENAI_API_KEY\",
OPENAI_API_ENDPOINT: \"$OPENAI_API_ENDPOINT\", OPENAI_API_ENDPOINT: \"$OPENAI_API_ENDPOINT\",
LLM_MODEL_NAME: \"$LLM_MODEL_NAME\", LLM_MODEL_NAME: \"$LLM_MODEL_NAME\",
HIDE_BUCKLE_DOT_DEV: \"$HIDE_BUCKLE_DOT_DEV\" HIDE_CHARTDB_CLOUD: \"$HIDE_CHARTDB_CLOUD\",
DISABLE_ANALYTICS: \"$DISABLE_ANALYTICS\"
};"; };";
} }

View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# Replace placeholders in nginx.conf # 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 envsubst '${OPENAI_API_KEY} ${OPENAI_API_ENDPOINT} ${LLM_MODEL_NAME} ${HIDE_CHARTDB_CLOUD} ${DISABLE_ANALYTICS}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
# Start Nginx # Start Nginx
nginx -g "daemon off;" nginx -g "daemon off;"

View File

@@ -4,8 +4,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="max-image-preview:large" /> <meta name="robots" content="noindex, max-image-preview:large" />
<title>ChartDB - Create & Visualize Database Schema Diagrams</title> <title>ChartDB - Create & Visualize Database Schema Diagrams</title>
<link rel="canonical" href="https://chartdb.io" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
@@ -13,11 +14,26 @@
rel="stylesheet" rel="stylesheet"
/> />
<script src="/config.js"></script> <script src="/config.js"></script>
<script <script>
src="https://cdn.usefathom.com/script.js" // Load analytics only if not disabled
data-site="PRHIVBNN" (function () {
defer const disableAnalytics =
></script> (window.env && window.env.DISABLE_ANALYTICS === 'true') ||
(typeof process !== 'undefined' &&
process.env &&
process.env.VITE_DISABLE_ANALYTICS === 'true');
if (!disableAnalytics) {
const script = document.createElement('script');
script.src = 'https://cdn.usefathom.com/script.js';
script.setAttribute('data-site', 'PRHIVBNN');
script.setAttribute('data-canonical', 'false');
script.setAttribute('data-spa', 'auto');
script.defer = true;
document.head.appendChild(script);
}
})();
</script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

1885
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "chartdb", "name": "chartdb",
"private": true, "private": true,
"version": "1.13.0", "version": "1.15.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -9,11 +9,15 @@
"lint": "eslint . --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --report-unused-disable-directives --max-warnings 0",
"lint:fix": "npm run lint -- --fix", "lint:fix": "npm run lint -- --fix",
"preview": "vite preview", "preview": "vite preview",
"prepare": "husky" "prepare": "husky",
"test": "vitest",
"test:ci": "vitest run --reporter=verbose --bail=1",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^0.0.51", "@ai-sdk/openai": "^0.0.51",
"@dbml/core": "^3.9.5", "@dbml/core": "^3.13.9",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
@@ -22,24 +26,24 @@
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1", "@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "1.2.0", "@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-select": "^2.1.1", "@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.2.7",
"@uidotdev/usehooks": "^2.4.1", "@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.3.1", "@xyflow/react": "^12.8.2",
"ahooks": "^3.8.1", "ahooks": "^3.8.1",
"ai": "^3.3.14", "ai": "^3.3.14",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@@ -50,8 +54,9 @@
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"i18next": "^23.14.0", "i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.441.0", "lucide-react": "^0.525.0",
"monaco-editor": "^0.52.0", "monaco-editor": "^0.52.0",
"motion": "^12.23.6",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"node-sql-parser": "^5.3.2", "node-sql-parser": "^5.3.2",
"react": "^18.3.1", "react": "^18.3.1",
@@ -73,12 +78,16 @@
"@eslint/compat": "^1.2.4", "@eslint/compat": "^1.2.4",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.16.0", "@eslint/js": "^9.16.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^22.1.0", "@types/node": "^22.1.0",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.18.0", "@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0", "@typescript-eslint/parser": "^8.18.0",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"@vitest/ui": "^3.2.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@@ -90,6 +99,7 @@
"eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-react-refresh": "^0.4.7",
"eslint-plugin-tailwindcss": "^3.17.4", "eslint-plugin-tailwindcss": "^3.17.4",
"globals": "^15.13.0", "globals": "^15.13.0",
"happy-dom": "^18.0.1",
"husky": "^9.1.5", "husky": "^9.1.5",
"postcss": "^8.4.40", "postcss": "^8.4.40",
"prettier": "^3.3.3", "prettier": "^3.3.3",
@@ -97,6 +107,7 @@
"tailwindcss": "^3.4.7", "tailwindcss": "^3.4.7",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"unplugin-inject-preload": "^3.0.0", "unplugin-inject-preload": "^3.0.0",
"vite": "^5.3.4" "vite": "^5.3.4",
"vitest": "^3.2.4"
} }
} }

View File

@@ -1,4 +1,4 @@
User-agent: * User-agent: *
Allow: / Disallow: /
Sitemap: https://app.chartdb.io/sitemap.xml Sitemap: https://app.chartdb.io/sitemap.xml

View File

@@ -1,7 +1,7 @@
import { cva } from 'class-variance-authority'; import { cva } from 'class-variance-authority';
export const buttonVariants = cva( export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -0,0 +1,137 @@
import React from 'react';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { Slot } from '@radix-ui/react-slot';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { buttonVariants } from './button-variants';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/dropdown-menu/dropdown-menu';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/tooltip/tooltip';
export interface ButtonAlternative {
label: string;
onClick: () => void;
disabled?: boolean;
icon?: React.ReactNode;
className?: string;
tooltip?: string;
}
export interface ButtonWithAlternativesProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
alternatives: Array<ButtonAlternative>;
dropdownTriggerClassName?: string;
chevronDownIconClassName?: string;
}
const ButtonWithAlternatives = React.forwardRef<
HTMLButtonElement,
ButtonWithAlternativesProps
>(
(
{
className,
variant,
size,
asChild = false,
alternatives,
children,
onClick,
dropdownTriggerClassName,
chevronDownIconClassName,
...props
},
ref
) => {
const Comp = asChild ? Slot : 'button';
const hasAlternatives = (alternatives?.length ?? 0) > 0;
return (
<div className="inline-flex items-stretch">
<Comp
className={cn(
buttonVariants({ variant, size }),
{ 'rounded-r-none': hasAlternatives },
className
)}
ref={ref}
onClick={onClick}
{...props}
>
{children}
</Comp>
{hasAlternatives ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={cn(
buttonVariants({ variant, size }),
'rounded-l-none border-l border-l-primary/5 px-2 min-w-0',
className?.includes('h-') &&
className.match(/h-\d+/)?.[0],
className?.includes('text-') &&
className.match(/text-\w+/)?.[0],
dropdownTriggerClassName
)}
type="button"
>
<ChevronDownIcon
className={cn(
'size-4 shrink-0',
chevronDownIconClassName
)}
/>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{alternatives.map((alternative, index) => {
const menuItem = (
<DropdownMenuItem
key={index}
onClick={alternative.onClick}
disabled={alternative.disabled}
className={cn(alternative.className)}
>
<span className="flex w-full items-center justify-between gap-2">
{alternative.label}
{alternative.icon}
</span>
</DropdownMenuItem>
);
if (alternative.tooltip) {
return (
<Tooltip key={index}>
<TooltipTrigger asChild>
{menuItem}
</TooltipTrigger>
<TooltipContent side="left">
{alternative.tooltip}
</TooltipContent>
</Tooltip>
);
}
return menuItem;
})}
</DropdownMenuContent>
</DropdownMenu>
) : null}
</div>
);
}
);
ButtonWithAlternatives.displayName = 'ButtonWithAlternatives';
export { ButtonWithAlternatives };

View File

@@ -31,6 +31,7 @@ export interface CodeSnippetAction {
label: string; label: string;
icon: LucideIcon; icon: LucideIcon;
onClick: () => void; onClick: () => void;
className?: string;
} }
export interface CodeSnippetProps { export interface CodeSnippetProps {
@@ -43,6 +44,8 @@ export interface CodeSnippetProps {
isComplete?: boolean; isComplete?: boolean;
editorProps?: React.ComponentProps<EditorType>; editorProps?: React.ComponentProps<EditorType>;
actions?: CodeSnippetAction[]; actions?: CodeSnippetAction[];
actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left';
allowCopy?: boolean;
} }
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo( export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
@@ -56,6 +59,8 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
isComplete = true, isComplete = true,
editorProps, editorProps,
actions, actions,
actionsTooltipSide,
allowCopy = true,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const monaco = useMonaco(); const monaco = useMonaco();
@@ -129,33 +134,37 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
{isComplete ? ( {isComplete ? (
<div className="absolute right-1 top-1 z-10 flex flex-col gap-1"> <div className="absolute right-1 top-1 z-10 flex flex-col gap-1">
<Tooltip {allowCopy ? (
onOpenChange={setTooltipOpen} <Tooltip
open={isCopied || tooltipOpen} onOpenChange={setTooltipOpen}
> open={isCopied || tooltipOpen}
<TooltipTrigger asChild> >
<span> <TooltipTrigger asChild>
<Button <span>
className="h-fit p-1.5" <Button
variant="outline" className="h-fit p-1.5"
onClick={copyToClipboard} variant="outline"
> onClick={copyToClipboard}
{isCopied ? ( >
<CopyCheck size={16} /> {isCopied ? (
) : ( <CopyCheck size={16} />
<Copy size={16} /> ) : (
)} <Copy size={16} />
</Button> )}
</span> </Button>
</TooltipTrigger> </span>
<TooltipContent> </TooltipTrigger>
{t( <TooltipContent
isCopied side={actionsTooltipSide}
? 'copied' >
: 'copy_to_clipboard' {t(
)} isCopied
</TooltipContent> ? 'copied'
</Tooltip> : 'copy_to_clipboard'
)}
</TooltipContent>
</Tooltip>
) : null}
{actions && {actions &&
actions.length > 0 && actions.length > 0 &&
@@ -164,7 +173,10 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span> <span>
<Button <Button
className="h-fit p-1.5" className={cn(
'h-fit p-1.5',
action.className
)}
variant="outline" variant="outline"
onClick={action.onClick} onClick={action.onClick}
> >
@@ -174,7 +186,9 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
</Button> </Button>
</span> </span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent
side={actionsTooltipSide}
>
{action.label} {action.label}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>

View File

@@ -0,0 +1,51 @@
import type { DBMLError } from '@/lib/dbml/dbml-import/dbml-import-error';
import * as monaco from 'monaco-editor';
export const highlightErrorLine = ({
error,
model,
editorDecorationsCollection,
}: {
error: DBMLError;
model?: monaco.editor.ITextModel | null;
editorDecorationsCollection:
| monaco.editor.IEditorDecorationsCollection
| undefined;
}) => {
if (!model) return;
if (!editorDecorationsCollection) 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',
},
},
},
];
editorDecorationsCollection?.set(decorations);
};
export const clearErrorHighlight = (
editorDecorationsCollection:
| monaco.editor.IEditorDecorationsCollection
| undefined
) => {
if (editorDecorationsCollection) {
editorDecorationsCollection.clear();
}
};

View File

@@ -37,18 +37,28 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
const datatypePattern = dataTypesNames.join('|'); const datatypePattern = dataTypesNames.join('|');
monaco.languages.setMonarchTokensProvider('dbml', { monaco.languages.setMonarchTokensProvider('dbml', {
keywords: ['Table', 'Ref', 'Indexes'], keywords: ['Table', 'Ref', 'Indexes', 'Note', 'Enum'],
datatypes: dataTypesNames, datatypes: dataTypesNames,
tokenizer: { tokenizer: {
root: [ root: [
[/\b(Table|Ref|Indexes)\b/, 'keyword'], [
/\b([Tt][Aa][Bb][Ll][Ee]|[Ee][Nn][Uu][Mm]|[Rr][Ee][Ff]|[Ii][Nn][Dd][Ee][Xx][Ee][Ss]|[Nn][Oo][Tt][Ee])\b/,
'keyword',
],
[/\[.*?\]/, 'annotation'], [/\[.*?\]/, 'annotation'],
[/'''/, 'string', '@tripleQuoteString'],
[/".*?"/, 'string'], [/".*?"/, 'string'],
[/'.*?'/, 'string'], [/'.*?'/, 'string'],
[/`.*?`/, 'string'],
[/[{}]/, 'delimiter'], [/[{}]/, 'delimiter'],
[/[<>]/, 'operator'], [/[<>]/, 'operator'],
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching [new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching
], ],
tripleQuoteString: [
[/[^']+/, 'string'],
[/'''/, 'string', '@pop'],
[/'/, 'string'],
],
}, },
}); });
}; };

View File

@@ -5,27 +5,45 @@ import {
PopoverTrigger, PopoverTrigger,
} from '@/components/popover/popover'; } from '@/components/popover/popover';
import { colorOptions } from '@/lib/colors'; import { colorOptions } from '@/lib/colors';
import { cn } from '@/lib/utils';
export interface ColorPickerProps { export interface ColorPickerProps {
color: string; color: string;
onChange: (color: string) => void; onChange: (color: string) => void;
disabled?: boolean;
popoverOnMouseDown?: (e: React.MouseEvent) => void;
popoverOnClick?: (e: React.MouseEvent) => void;
} }
export const ColorPicker = React.forwardRef< export const ColorPicker = React.forwardRef<
React.ElementRef<typeof PopoverTrigger>, React.ElementRef<typeof PopoverTrigger>,
ColorPickerProps ColorPickerProps
>(({ color, onChange }, ref) => { >(({ color, onChange, disabled, popoverOnMouseDown, popoverOnClick }, ref) => {
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild ref={ref}> <PopoverTrigger
asChild
ref={ref}
disabled={disabled}
{...(disabled ? { onClick: (e) => e.preventDefault() } : {})}
>
<div <div
className="h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md" className={cn(
'h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md',
{
'hover:shadow-none cursor-default': disabled,
}
)}
style={{ style={{
backgroundColor: color, backgroundColor: color,
}} }}
/> />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-fit"> <PopoverContent
className="w-fit"
onMouseDown={popoverOnMouseDown}
onClick={popoverOnClick}
>
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{colorOptions.map((option) => ( {colorOptions.map((option) => (
<div <div

View File

@@ -4,6 +4,7 @@ import { Cross2Icon } from '@radix-ui/react-icons';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ScrollArea } from '../scroll-area/scroll-area'; import { ScrollArea } from '../scroll-area/scroll-area';
import { ChevronLeft } from 'lucide-react';
const Dialog = DialogPrimitive.Root; const Dialog = DialogPrimitive.Root;
@@ -32,28 +33,75 @@ const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
showClose?: boolean; showClose?: boolean;
showBack?: boolean;
backButtonClassName?: string;
blurBackground?: boolean;
forceOverlay?: boolean;
onBackClick?: () => void;
} }
>(({ className, children, showClose, ...props }, ref) => ( >(
<DialogPortal> (
<DialogOverlay /> {
<DialogPrimitive.Content className,
ref={ref} children,
className={cn( showClose,
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', showBack,
className onBackClick,
)} backButtonClassName,
{...props} blurBackground,
> forceOverlay,
{children} ...props
{showClose && ( },
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> ref
<Cross2Icon className="size-4" /> ) => (
<span className="sr-only">Close</span> <DialogPortal>
</DialogPrimitive.Close> {forceOverlay ? (
)} <div
</DialogPrimitive.Content> className={cn(
</DialogPortal> 'fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
)); {
'bg-black/80': !blurBackground,
'bg-black/30 backdrop-blur-sm': blurBackground,
}
)}
data-state="open"
/>
) : null}
<DialogOverlay
className={cn({
'bg-black/30 backdrop-blur-sm': blurBackground,
})}
/>
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
>
{children}
{showBack && (
<button
onClick={() => onBackClick?.()}
className={cn(
'absolute left-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground',
backButtonClassName
)}
>
<ChevronLeft className="size-4" />
</button>
)}
{showClose && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="size-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
);
DialogContent.displayName = DialogPrimitive.Content.displayName; DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({

View File

@@ -52,7 +52,7 @@ export const EmptyState = forwardRef<
</Label> </Label>
<Label <Label
className={cn( className={cn(
'text-sm font-normal text-muted-foreground', 'text-sm text-center font-normal text-muted-foreground',
descriptionClassName descriptionClassName
)} )}
> >

View File

@@ -2,16 +2,13 @@ import React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
export interface InputProps const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => { ({ className, type, ...props }, ref) => {
return ( return (
<input <input
type={type} type={type}
className={cn( className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className className
)} )}
ref={ref} ref={ref}

View File

@@ -0,0 +1,121 @@
import React from 'react';
import { cn } from '@/lib/utils';
import type { ButtonProps } from '../button/button';
import { buttonVariants } from '../button/button-variants';
import {
ChevronLeftIcon,
ChevronRightIcon,
DotsHorizontalIcon,
} from '@radix-ui/react-icons';
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
<nav
role="navigation"
aria-label="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
);
Pagination.displayName = 'Pagination';
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<'ul'>
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
));
PaginationContent.displayName = 'PaginationContent';
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<'li'>
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn('', className)} {...props} />
));
PaginationItem.displayName = 'PaginationItem';
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, 'size'> &
React.ComponentProps<'a'>;
const PaginationLink = ({
className,
isActive,
size = 'icon',
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? 'page' : undefined}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size,
}),
className
)}
{...props}
/>
);
PaginationLink.displayName = 'PaginationLink';
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn('gap-1 pl-2.5', className)}
{...props}
>
<ChevronLeftIcon className="size-4" />
<span>Previous</span>
</PaginationLink>
);
PaginationPrevious.displayName = 'PaginationPrevious';
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn('gap-1 pr-2.5', className)}
{...props}
>
<span>Next</span>
<ChevronRightIcon className="size-4" />
</PaginationLink>
);
PaginationNext.displayName = 'PaginationNext';
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<'span'>) => (
<span
aria-hidden
className={cn('flex h-9 w-9 items-center justify-center', className)}
{...props}
>
<DotsHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
);
PaginationEllipsis.displayName = 'PaginationEllipsis';
export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
};

View File

@@ -27,6 +27,7 @@ export interface SelectBoxOption {
regex?: string; regex?: string;
extractRegex?: RegExp; extractRegex?: RegExp;
group?: string; group?: string;
icon?: React.ReactNode;
} }
export interface SelectBoxProps { export interface SelectBoxProps {
@@ -53,6 +54,10 @@ export interface SelectBoxProps {
open?: boolean; open?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
popoverClassName?: string; popoverClassName?: string;
readonly?: boolean;
footerButtons?: React.ReactNode;
commandOnMouseDown?: (e: React.MouseEvent) => void;
commandOnClick?: (e: React.MouseEvent) => void;
} }
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>( export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
@@ -78,6 +83,10 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
open, open,
onOpenChange: setOpen, onOpenChange: setOpen,
popoverClassName, popoverClassName,
readonly,
footerButtons,
commandOnMouseDown,
commandOnClick,
}, },
ref ref
) => { ) => {
@@ -93,6 +102,12 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
(isOpen: boolean) => { (isOpen: boolean) => {
setOpen?.(isOpen); setOpen?.(isOpen);
setIsOpen(isOpen); setIsOpen(isOpen);
if (isOpen) {
setSearchTerm('');
}
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
}, },
[setOpen] [setOpen]
); );
@@ -146,18 +161,20 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`} className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`}
> >
<span>{option.label}</span> <span>{option.label}</span>
<span {!readonly ? (
onClick={(e) => { <span
e.preventDefault(); onClick={(e) => {
handleSelect(option.value); e.preventDefault();
}} handleSelect(option.value);
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground" }}
> className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
<Cross2Icon /> >
</span> <Cross2Icon />
</span>
) : null}
</span> </span>
)), )),
[options, value, handleSelect, oneLine, keepOrder] [options, value, handleSelect, oneLine, keepOrder, readonly]
); );
const isAllSelected = React.useMemo( const isAllSelected = React.useMemo(
@@ -227,9 +244,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
onSelect={() => onSelect={() =>
handleSelect( handleSelect(
option.value, option.value,
matches?.map((match) => match.toString()) matches?.map((match) => match?.toString())
) )
} }
onMouseDown={commandOnMouseDown}
onClick={commandOnClick}
> >
{multiple && ( {multiple && (
<div <div
@@ -244,6 +263,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</div> </div>
)} )}
<div className="flex flex-1 items-center truncate"> <div className="flex flex-1 items-center truncate">
{option.icon ? (
<span className="mr-2 shrink-0">
{option.icon}
</span>
) : null}
<span> <span>
{isRegexMatch ? searchTerm : option.label} {isRegexMatch ? searchTerm : option.label}
{!isRegexMatch && optionSuffix {!isRegexMatch && optionSuffix
@@ -270,7 +294,15 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</CommandItem> </CommandItem>
); );
}, },
[value, multiple, searchTerm, handleSelect, optionSuffix] [
value,
multiple,
searchTerm,
handleSelect,
optionSuffix,
commandOnClick,
commandOnMouseDown,
]
); );
return ( return (
@@ -278,7 +310,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}> <PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
<div <div
className={cn( className={cn(
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`, `flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''} ${readonly ? 'pointer-events-none' : ''}`,
className className
)} )}
> >
@@ -348,6 +380,8 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
popoverClassName popoverClassName
)} )}
align="center" align="center"
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
> >
<Command <Command
filter={(value, search, keywords) => { filter={(value, search, keywords) => {
@@ -418,30 +452,28 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
<ScrollArea> <ScrollArea>
<div className="max-h-64 w-full"> <div className="max-h-64 w-full">
<CommandGroup> <CommandList className="max-h-fit w-full">
<CommandList className="max-h-fit w-full"> {hasGroups
{hasGroups ? Object.entries(groups).map(
? Object.entries(groups).map( ([groupName, groupOptions]) => (
([ <CommandGroup
groupName, key={groupName}
groupOptions, heading={groupName}
]) => ( >
<CommandGroup {groupOptions.map(
key={groupName} renderOption
heading={groupName} )}
> </CommandGroup>
{groupOptions.map(
renderOption
)}
</CommandGroup>
)
) )
: options.map(renderOption)} )
</CommandList> : options.map(renderOption)}
</CommandGroup> </CommandList>
</div> </div>
</ScrollArea> </ScrollArea>
</Command> </Command>
{footerButtons ? (
<div className="border-t">{footerButtons}</div>
) : null}
</PopoverContent> </PopoverContent>
</Popover> </Popover>
); );

View File

@@ -29,6 +29,7 @@ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = '16rem'; const SIDEBAR_WIDTH = '16rem';
const SIDEBAR_WIDTH_MOBILE = '18rem'; const SIDEBAR_WIDTH_MOBILE = '18rem';
const SIDEBAR_WIDTH_ICON = '3rem'; const SIDEBAR_WIDTH_ICON = '3rem';
const SIDEBAR_WIDTH_ICON_EXTENDED = '4rem';
const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
type SidebarContext = { type SidebarContext = {
@@ -142,6 +143,8 @@ const SidebarProvider = React.forwardRef<
{ {
'--sidebar-width': SIDEBAR_WIDTH, '--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON, '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
'--sidebar-width-icon-extended':
SIDEBAR_WIDTH_ICON_EXTENDED,
...style, ...style,
} as React.CSSProperties } as React.CSSProperties
} }
@@ -166,7 +169,7 @@ const Sidebar = React.forwardRef<
React.ComponentProps<'div'> & { React.ComponentProps<'div'> & {
side?: 'left' | 'right'; side?: 'left' | 'right';
variant?: 'sidebar' | 'floating' | 'inset'; variant?: 'sidebar' | 'floating' | 'inset';
collapsible?: 'offcanvas' | 'icon' | 'none'; collapsible?: 'offcanvas' | 'icon' | 'icon-extended' | 'none';
} }
>( >(
( (
@@ -245,8 +248,8 @@ const Sidebar = React.forwardRef<
'group-data-[collapsible=offcanvas]:w-0', 'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180', 'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset' variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]' ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]' : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended]'
)} )}
/> />
<div <div
@@ -257,8 +260,8 @@ const Sidebar = React.forwardRef<
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]', : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants. // Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset' variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]' ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l', : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended] group-data-[side=left]:border-r group-data-[side=right]:border-l',
className className
)} )}
{...props} {...props}
@@ -421,7 +424,7 @@ const SidebarContent = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="content" data-sidebar="content"
className={cn( className={cn(
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden group-data-[collapsible=icon-extended]:overflow-hidden',
className className
)} )}
{...props} {...props}
@@ -461,6 +464,7 @@ const SidebarGroupLabel = React.forwardRef<
className={cn( className={cn(
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', 'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0', 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
'group-data-[collapsible=icon-extended]:-mt-8 group-data-[collapsible=icon-extended]:opacity-0',
className className
)} )}
{...props} {...props}
@@ -483,7 +487,7 @@ const SidebarGroupAction = React.forwardRef<
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', 'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden', 'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden', 'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
className className
)} )}
{...props} {...props}
@@ -532,7 +536,7 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = 'SidebarMenuItem'; SidebarMenuItem.displayName = 'SidebarMenuItem';
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon-extended]:h-auto group-data-[collapsible=icon-extended]:flex-col group-data-[collapsible=icon-extended]:gap-1 group-data-[collapsible=icon-extended]:p-2 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate group-data-[collapsible=icon-extended]:[&>span]:w-full group-data-[collapsible=icon-extended]:[&>span]:text-center group-data-[collapsible=icon-extended]:[&>span]:text-[10px] group-data-[collapsible=icon-extended]:[&>span]:leading-tight [&>svg]:size-4 [&>svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {
@@ -636,7 +640,7 @@ const SidebarMenuAction = React.forwardRef<
'peer-data-[size=sm]/menu-button:top-1', 'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5', 'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5', 'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden', 'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
showOnHover && showOnHover &&
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0', 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
className className
@@ -753,7 +757,7 @@ const SidebarMenuSubButton = React.forwardRef<
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground', 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs', size === 'sm' && 'text-xs',
size === 'md' && 'text-sm', size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden', 'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
className className
)} )}
{...props} {...props}

View File

@@ -20,6 +20,7 @@ export function Toaster() {
description, description,
action, action,
layout = 'row', layout = 'row',
hideCloseButton = false,
...props ...props
}) { }) {
return ( return (
@@ -38,7 +39,7 @@ export function Toaster() {
) : null} ) : null}
</div> </div>
{layout === 'row' ? action : null} {layout === 'row' ? action : null}
<ToastClose /> {!hideCloseButton ? <ToastClose /> : null}
</Toast> </Toast>
); );
})} })}

View File

@@ -12,6 +12,7 @@ type ToasterToast = ToastProps & {
description?: React.ReactNode; description?: React.ReactNode;
action?: ToastActionElement; action?: ToastActionElement;
layout?: 'row' | 'column'; layout?: 'row' | 'column';
hideCloseButton?: boolean;
}; };
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars

View File

@@ -13,15 +13,17 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>, React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => ( >(({ className, sideOffset = 4, ...props }, ref) => (
// <TooltipPrimitive.Portal>
<TooltipPrimitive.Content <TooltipPrimitive.Content
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]',
className className
)} )}
{...props} {...props}
/> />
// </TooltipPrimitive.Portal>
)); ));
TooltipContent.displayName = TooltipPrimitive.Content.displayName; TooltipContent.displayName = TooltipPrimitive.Content.displayName;

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { Skeleton } from '../skeleton/skeleton';
import { cn } from '@/lib/utils';
export interface TreeItemSkeletonProps
extends React.HTMLAttributes<HTMLDivElement> {}
export const TreeItemSkeleton: React.FC<TreeItemSkeletonProps> = ({
className,
style,
}) => {
return (
<div className={cn('px-2 py-1', className)} style={style}>
<Skeleton className="h-3.5 w-full rounded-sm" />
</div>
);
};

View File

@@ -0,0 +1,461 @@
import {
ChevronRight,
File,
Folder,
Loader2,
type LucideIcon,
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { cn } from '@/lib/utils';
import { Button } from '@/components/button/button';
import type {
TreeNode,
FetchChildrenFunction,
SelectableTreeProps,
} from './tree';
import type { ExpandedState } from './use-tree';
import { useTree } from './use-tree';
import type { Dispatch, ReactNode, SetStateAction } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { TreeItemSkeleton } from './tree-item-skeleton';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/tooltip/tooltip';
interface TreeViewProps<
Type extends string,
Context extends Record<Type, unknown>,
> {
data: TreeNode<Type, Context>[];
fetchChildren?: FetchChildrenFunction<Type, Context>;
onNodeClick?: (node: TreeNode<Type, Context>) => void;
className?: string;
defaultIcon?: LucideIcon;
defaultFolderIcon?: LucideIcon;
defaultIconProps?: React.ComponentProps<LucideIcon>;
defaultFolderIconProps?: React.ComponentProps<LucideIcon>;
selectable?: SelectableTreeProps<Type, Context>;
expanded?: ExpandedState;
setExpanded?: Dispatch<SetStateAction<ExpandedState>>;
renderHoverComponent?: (node: TreeNode<Type, Context>) => ReactNode;
renderActionsComponent?: (node: TreeNode<Type, Context>) => ReactNode;
loadingNodeIds?: string[];
}
export function TreeView<
Type extends string,
Context extends Record<Type, unknown>,
>({
data,
fetchChildren,
onNodeClick,
className,
defaultIcon = File,
defaultFolderIcon = Folder,
defaultIconProps,
defaultFolderIconProps,
selectable,
expanded: expandedProp,
setExpanded: setExpandedProp,
renderHoverComponent,
renderActionsComponent,
loadingNodeIds,
}: TreeViewProps<Type, Context>) {
const { expanded, loading, loadedChildren, hasMoreChildren, toggleNode } =
useTree({
fetchChildren,
expanded: expandedProp,
setExpanded: setExpandedProp,
});
const [selectedIdInternal, setSelectedIdInternal] = React.useState<
string | undefined
>(selectable?.defaultSelectedId);
const selectedId = useMemo(() => {
return selectable?.selectedId ?? selectedIdInternal;
}, [selectable?.selectedId, selectedIdInternal]);
const setSelectedId = useCallback(
(value: SetStateAction<string | undefined>) => {
if (selectable?.setSelectedId) {
selectable.setSelectedId(value);
} else {
setSelectedIdInternal(value);
}
},
[selectable, setSelectedIdInternal]
);
useEffect(() => {
if (selectable?.enabled && selectable.defaultSelectedId) {
if (selectable.defaultSelectedId === selectedId) return;
setSelectedId(selectable.defaultSelectedId);
const { node, path } = findNodeById(
data,
selectable.defaultSelectedId
);
if (node) {
selectable.onSelectedChange?.(node);
// Expand all parent nodes
for (const parent of path) {
if (expanded[parent.id]) continue;
toggleNode(
parent.id,
parent.type,
parent.context,
parent.children
);
}
}
}
}, [selectable, toggleNode, selectedId, data, expanded, setSelectedId]);
const handleNodeSelect = (node: TreeNode<Type, Context>) => {
if (selectable?.enabled) {
setSelectedId(node.id);
selectable.onSelectedChange?.(node);
}
};
return (
<div className={cn('w-full', className)}>
{data.map((node, index) => (
<TreeNode
key={node.id}
node={node}
level={0}
expanded={expanded}
loading={loading}
loadedChildren={loadedChildren}
hasMoreChildren={hasMoreChildren}
onToggle={toggleNode}
onNodeClick={onNodeClick}
defaultIcon={defaultIcon}
defaultFolderIcon={defaultFolderIcon}
defaultIconProps={defaultIconProps}
defaultFolderIconProps={defaultFolderIconProps}
selectable={selectable?.enabled}
selectedId={selectedId}
onSelect={handleNodeSelect}
className={index > 0 ? 'mt-0.5' : ''}
renderHoverComponent={renderHoverComponent}
renderActionsComponent={renderActionsComponent}
loadingNodeIds={loadingNodeIds}
/>
))}
</div>
);
}
interface TreeNodeProps<
Type extends string,
Context extends Record<Type, unknown>,
> {
node: TreeNode<Type, Context>;
level: number;
expanded: Record<string, boolean>;
loading: Record<string, boolean>;
loadedChildren: Record<string, TreeNode<Type, Context>[]>;
hasMoreChildren: Record<string, boolean>;
onToggle: (
nodeId: string,
nodeType: Type,
nodeContext: Context[Type],
staticChildren?: TreeNode<Type, Context>[]
) => void;
onNodeClick?: (node: TreeNode<Type, Context>) => void;
defaultIcon: LucideIcon;
defaultFolderIcon: LucideIcon;
defaultIconProps?: React.ComponentProps<LucideIcon>;
defaultFolderIconProps?: React.ComponentProps<LucideIcon>;
selectable?: boolean;
selectedId?: string;
onSelect: (node: TreeNode<Type, Context>) => void;
className?: string;
renderHoverComponent?: (node: TreeNode<Type, Context>) => ReactNode;
renderActionsComponent?: (node: TreeNode<Type, Context>) => ReactNode;
loadingNodeIds?: string[];
}
function TreeNode<Type extends string, Context extends Record<Type, unknown>>({
node,
level,
expanded,
loading,
loadedChildren,
hasMoreChildren,
onToggle,
onNodeClick,
defaultIcon: DefaultIcon,
defaultFolderIcon: DefaultFolderIcon,
defaultIconProps,
defaultFolderIconProps,
selectable,
selectedId,
onSelect,
className,
renderHoverComponent,
renderActionsComponent,
loadingNodeIds,
}: TreeNodeProps<Type, Context>) {
const [isHovered, setIsHovered] = useState(false);
const isExpanded = expanded[node.id];
const isLoading = loading[node.id];
const children = loadedChildren[node.id] || node.children;
const isSelected = selectedId === node.id;
const IconComponent =
node.icon || (node.isFolder ? DefaultFolderIcon : DefaultIcon);
const iconProps: React.ComponentProps<LucideIcon> = {
strokeWidth: isSelected ? 2.5 : 2,
...(node.isFolder ? defaultFolderIconProps : defaultIconProps),
...node.iconProps,
className: cn(
'h-3.5 w-3.5 text-muted-foreground flex-none',
isSelected && 'text-primary text-white',
node.iconProps?.className
),
};
return (
<div className={cn(className)}>
<div
className={cn(
'flex items-center gap-1.5 px-2 py-1 rounded-lg cursor-pointer group h-6',
'transition-colors duration-200',
isSelected
? 'bg-sky-500 border border-sky-600 border dark:bg-sky-600 dark:border-sky-700'
: 'hover:bg-gray-200/50 border border-transparent dark:hover:bg-gray-700/50',
node.className
)}
{...(isSelected ? { 'data-selected': true } : {})}
style={{ paddingLeft: `${level * 16 + 8}px` }}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={(e) => {
e.stopPropagation();
if (selectable && !node.unselectable) {
onSelect(node);
}
// if (node.isFolder) {
// onToggle(node.id, node.children);
// }
// called only once in case of double click
if (e.detail !== 2) {
onNodeClick?.(node);
}
}}
onDoubleClick={(e) => {
e.stopPropagation();
if (node.isFolder) {
onToggle(
node.id,
node.type,
node.context,
node.children
);
}
}}
>
<div className="flex flex-none items-center gap-1.5">
<Button
variant="ghost"
size="icon"
className={cn(
'h-3.5 w-3.5 p-0 hover:bg-transparent flex-none',
isExpanded && 'rotate-90',
'transition-transform duration-200'
)}
onClick={(e) => {
e.stopPropagation();
if (node.isFolder) {
onToggle(
node.id,
node.type,
node.context,
node.children
);
}
}}
>
{node.isFolder &&
(isLoading ? (
<Loader2
className={cn('size-3.5 animate-spin', {
'text-white': isSelected,
})}
/>
) : (
<ChevronRight
className={cn('size-3.5', {
'text-white': isSelected,
})}
strokeWidth={2}
/>
))}
</Button>
{node.tooltip ? (
<Tooltip>
<TooltipTrigger asChild>
{loadingNodeIds?.includes(node.id) ? (
<Loader2
className={cn('size-3.5 animate-spin', {
'text-white': isSelected,
})}
/>
) : (
<IconComponent
{...(isSelected
? { 'data-selected': true }
: {})}
{...iconProps}
/>
)}
</TooltipTrigger>
<TooltipContent
align="center"
className="max-w-[400px]"
>
{node.tooltip}
</TooltipContent>
</Tooltip>
) : node.empty ? null : loadingNodeIds?.includes(
node.id
) ? (
<Loader2
className={cn('size-3.5 animate-spin', {
// 'text-white': isSelected,
})}
/>
) : (
<IconComponent
{...(isSelected ? { 'data-selected': true } : {})}
{...iconProps}
/>
)}
</div>
<span
{...node.labelProps}
className={cn(
'text-xs truncate min-w-0 flex-1 w-0',
isSelected && 'font-medium text-primary text-white',
node.labelProps?.className
)}
{...(isSelected ? { 'data-selected': true } : {})}
>
{node.empty ? '' : node.name}
</span>
{renderActionsComponent && renderActionsComponent(node)}
{isHovered && renderHoverComponent
? renderHoverComponent(node)
: null}
</div>
<AnimatePresence initial={false}>
{isExpanded && children && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{
height: 'auto',
opacity: 1,
transition: {
height: {
duration: Math.min(
0.3 + children.length * 0.018,
0.7
),
ease: 'easeInOut',
},
opacity: {
duration: Math.min(
0.2 + children.length * 0.012,
0.4
),
ease: 'easeInOut',
},
},
}}
exit={{
height: 0,
opacity: 0,
transition: {
height: {
duration: Math.min(
0.2 + children.length * 0.01,
0.45
),
ease: 'easeInOut',
},
opacity: {
duration: 0.1,
ease: 'easeOut',
},
},
}}
style={{ overflow: 'hidden' }}
>
{children.map((child) => (
<TreeNode
key={child.id}
node={child}
level={level + 1}
expanded={expanded}
loading={loading}
loadedChildren={loadedChildren}
hasMoreChildren={hasMoreChildren}
onToggle={onToggle}
onNodeClick={onNodeClick}
defaultIcon={DefaultIcon}
defaultFolderIcon={DefaultFolderIcon}
defaultIconProps={defaultIconProps}
defaultFolderIconProps={defaultFolderIconProps}
selectable={selectable}
selectedId={selectedId}
onSelect={onSelect}
className="mt-0.5"
renderHoverComponent={renderHoverComponent}
renderActionsComponent={renderActionsComponent}
loadingNodeIds={loadingNodeIds}
/>
))}
{isLoading ? (
<TreeItemSkeleton
style={{
paddingLeft: `${level + 2 * 16 + 8}px`,
}}
/>
) : null}
</motion.div>
)}
</AnimatePresence>
</div>
);
}
function findNodeById<
Type extends string,
Context extends Record<Type, unknown>,
>(
nodes: TreeNode<Type, Context>[],
id: string,
initialPath: TreeNode<Type, Context>[] = []
): { node: TreeNode<Type, Context> | null; path: TreeNode<Type, Context>[] } {
const path: TreeNode<Type, Context>[] = [...initialPath];
for (const node of nodes) {
if (node.id === id) return { node, path };
if (node.children) {
const found = findNodeById(node.children, id, [...path, node]);
if (found.node) {
return found;
}
}
}
return { node: null, path };
}

View File

@@ -0,0 +1,41 @@
import type { LucideIcon } from 'lucide-react';
import type React from 'react';
export interface TreeNode<
Type extends string,
Context extends Record<Type, unknown>,
> {
id: string;
name: string;
isFolder?: boolean;
children?: TreeNode<Type, Context>[];
icon?: LucideIcon;
iconProps?: React.ComponentProps<LucideIcon>;
labelProps?: React.ComponentProps<'span'>;
type: Type;
unselectable?: boolean;
tooltip?: string;
context: Context[Type];
empty?: boolean;
className?: string;
}
export type FetchChildrenFunction<
Type extends string,
Context extends Record<Type, unknown>,
> = (
nodeId: string,
nodeType: Type,
nodeContext: Context[Type]
) => Promise<TreeNode<Type, Context>[]>;
export interface SelectableTreeProps<
Type extends string,
Context extends Record<Type, unknown>,
> {
enabled: boolean;
defaultSelectedId?: string;
onSelectedChange?: (node: TreeNode<Type, Context>) => void;
selectedId?: string;
setSelectedId?: React.Dispatch<React.SetStateAction<string | undefined>>;
}

View File

@@ -0,0 +1,153 @@
import type { Dispatch, SetStateAction } from 'react';
import { useState, useCallback, useMemo } from 'react';
import type { TreeNode, FetchChildrenFunction } from './tree';
export interface ExpandedState {
[key: string]: boolean;
}
interface LoadingState {
[key: string]: boolean;
}
interface LoadedChildren<
Type extends string,
Context extends Record<Type, unknown>,
> {
[key: string]: TreeNode<Type, Context>[];
}
interface HasMoreChildrenState {
[key: string]: boolean;
}
export function useTree<
Type extends string,
Context extends Record<Type, unknown>,
>({
fetchChildren,
expanded: expandedProp,
setExpanded: setExpandedProp,
}: {
fetchChildren?: FetchChildrenFunction<Type, Context>;
expanded?: ExpandedState;
setExpanded?: Dispatch<SetStateAction<ExpandedState>>;
}) {
const [expandedInternal, setExpandedInternal] = useState<ExpandedState>({});
const expanded = useMemo(
() => expandedProp ?? expandedInternal,
[expandedProp, expandedInternal]
);
const setExpanded = useCallback(
(value: SetStateAction<ExpandedState>) => {
if (setExpandedProp) {
setExpandedProp(value);
} else {
setExpandedInternal(value);
}
},
[setExpandedProp, setExpandedInternal]
);
const [loading, setLoading] = useState<LoadingState>({});
const [loadedChildren, setLoadedChildren] = useState<
LoadedChildren<Type, Context>
>({});
const [hasMoreChildren, setHasMoreChildren] =
useState<HasMoreChildrenState>({});
const mergeChildren = useCallback(
(
staticChildren: TreeNode<Type, Context>[] = [],
fetchedChildren: TreeNode<Type, Context>[] = []
) => {
const fetchedChildrenIds = new Set(
fetchedChildren.map((child) => child.id)
);
const uniqueStaticChildren = staticChildren.filter(
(child) => !fetchedChildrenIds.has(child.id)
);
return [...uniqueStaticChildren, ...fetchedChildren];
},
[]
);
const toggleNode = useCallback(
async (
nodeId: string,
nodeType: Type,
nodeContext: Context[Type],
staticChildren?: TreeNode<Type, Context>[]
) => {
if (expanded[nodeId]) {
// If we're collapsing, just update expanded state
setExpanded((prev) => ({ ...prev, [nodeId]: false }));
return;
}
// Get any previously fetched children
const previouslyFetchedChildren = loadedChildren[nodeId] || [];
// If we have static children, merge them with any previously fetched children
if (staticChildren?.length) {
const mergedChildren = mergeChildren(
staticChildren,
previouslyFetchedChildren
);
setLoadedChildren((prev) => ({
...prev,
[nodeId]: mergedChildren,
}));
// Only show "more loading" if we haven't fetched children before
setHasMoreChildren((prev) => ({
...prev,
[nodeId]: !previouslyFetchedChildren.length,
}));
}
// Set expanded state immediately to show static/previously fetched children
setExpanded((prev) => ({ ...prev, [nodeId]: true }));
// If we haven't loaded dynamic children yet
if (!previouslyFetchedChildren.length) {
setLoading((prev) => ({ ...prev, [nodeId]: true }));
try {
const fetchedChildren = await fetchChildren?.(
nodeId,
nodeType,
nodeContext
);
// Merge static and newly fetched children
const allChildren = mergeChildren(
staticChildren || [],
fetchedChildren
);
setLoadedChildren((prev) => ({
...prev,
[nodeId]: allChildren,
}));
setHasMoreChildren((prev) => ({
...prev,
[nodeId]: false,
}));
} catch (error) {
console.error('Error loading children:', error);
} finally {
setLoading((prev) => ({ ...prev, [nodeId]: false }));
}
}
},
[expanded, loadedChildren, fetchChildren, mergeChildren, setExpanded]
);
return {
expanded,
loading,
loadedChildren,
hasMoreChildren,
toggleNode,
};
}

View File

@@ -12,6 +12,18 @@ export interface CanvasContext {
}) => void; }) => void;
setOverlapGraph: (graph: Graph<string>) => void; setOverlapGraph: (graph: Graph<string>) => void;
overlapGraph: Graph<string>; overlapGraph: Graph<string>;
setShowFilter: React.Dispatch<React.SetStateAction<boolean>>;
showFilter: boolean;
editTableModeTable: {
tableId: string;
fieldId?: string;
} | null;
setEditTableModeTable: React.Dispatch<
React.SetStateAction<{
tableId: string;
fieldId?: string;
} | null>
>;
} }
export const canvasContext = createContext<CanvasContext>({ export const canvasContext = createContext<CanvasContext>({
@@ -19,4 +31,8 @@ export const canvasContext = createContext<CanvasContext>({
fitView: emptyFn, fitView: emptyFn,
setOverlapGraph: emptyFn, setOverlapGraph: emptyFn,
overlapGraph: createGraph(), overlapGraph: createGraph(),
setShowFilter: emptyFn,
showFilter: false,
editTableModeTable: null,
setEditTableModeTable: emptyFn,
}); });

View File

@@ -1,25 +1,59 @@
import React, { type ReactNode, useCallback, useState } from 'react'; import React, {
type ReactNode,
useCallback,
useState,
useEffect,
useRef,
} from 'react';
import { canvasContext } from './canvas-context'; import { canvasContext } from './canvas-context';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { import { adjustTablePositions } from '@/lib/domain/db-table';
adjustTablePositions,
shouldShowTablesBySchemaFilter,
} from '@/lib/domain/db-table';
import { useReactFlow } from '@xyflow/react'; import { useReactFlow } from '@xyflow/react';
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils'; import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
import type { Graph } from '@/lib/graph'; import type { Graph } from '@/lib/graph';
import { createGraph } from '@/lib/graph'; import { createGraph } from '@/lib/graph';
import { useDiagramFilter } from '../diagram-filter-context/use-diagram-filter';
import { filterTable } from '@/lib/domain/diagram-filter/filter';
import { defaultSchemas } from '@/lib/data/default-schemas';
interface CanvasProviderProps { interface CanvasProviderProps {
children: ReactNode; children: ReactNode;
} }
export const CanvasProvider = ({ children }: CanvasProviderProps) => { export const CanvasProvider = ({ children }: CanvasProviderProps) => {
const { tables, relationships, updateTablesState, filteredSchemas } = const {
useChartDB(); tables,
relationships,
updateTablesState,
databaseType,
areas,
diagramId,
} = useChartDB();
const { filter, loading: filterLoading } = useDiagramFilter();
const { fitView } = useReactFlow(); const { fitView } = useReactFlow();
const [overlapGraph, setOverlapGraph] = const [overlapGraph, setOverlapGraph] =
useState<Graph<string>>(createGraph()); useState<Graph<string>>(createGraph());
const [editTableModeTable, setEditTableModeTable] = useState<{
tableId: string;
fieldId?: string;
} | null>(null);
const [showFilter, setShowFilter] = useState(false);
const diagramIdActiveFilterRef = useRef<string>();
useEffect(() => {
if (filterLoading) {
return;
}
if (diagramIdActiveFilterRef.current === diagramId) {
return;
}
diagramIdActiveFilterRef.current = diagramId;
setShowFilter(true);
}, [filterLoading, diagramId]);
const reorderTables = useCallback( const reorderTables = useCallback(
( (
@@ -30,9 +64,19 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
const newTables = adjustTablePositions({ const newTables = adjustTablePositions({
relationships, relationships,
tables: tables.filter((table) => tables: tables.filter((table) =>
shouldShowTablesBySchemaFilter(table, filteredSchemas) filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[databaseType],
},
})
), ),
mode: 'all', // Use 'all' mode for manual reordering areas,
mode: 'all',
}); });
const updatedOverlapGraph = findOverlappingTables({ const updatedOverlapGraph = findOverlappingTables({
@@ -67,7 +111,15 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
}); });
}, 500); }, 500);
}, },
[filteredSchemas, relationships, tables, updateTablesState, fitView] [
filter,
relationships,
tables,
updateTablesState,
fitView,
databaseType,
areas,
]
); );
return ( return (
@@ -77,6 +129,10 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
fitView, fitView,
setOverlapGraph, setOverlapGraph,
overlapGraph, overlapGraph,
setShowFilter,
showFilter,
editTableModeTable,
setEditTableModeTable,
}} }}
> >
{children} {children}

View File

@@ -78,8 +78,8 @@ export interface ChartDBContext {
events: EventEmitter<ChartDBEvent>; events: EventEmitter<ChartDBEvent>;
readonly?: boolean; readonly?: boolean;
filteredSchemas?: string[]; highlightedCustomType?: DBCustomType;
filterSchemas: (schemaIds: string[]) => void; highlightCustomTypeId: (id?: string) => void;
// General operations // General operations
updateDiagramId: (id: string) => Promise<void>; updateDiagramId: (id: string) => Promise<void>;
@@ -92,6 +92,10 @@ export interface ChartDBContext {
updateDiagramUpdatedAt: () => Promise<void>; updateDiagramUpdatedAt: () => Promise<void>;
clearDiagramData: () => Promise<void>; clearDiagramData: () => Promise<void>;
deleteDiagram: () => Promise<void>; deleteDiagram: () => Promise<void>;
updateDiagramData: (
diagram: Diagram,
options?: { forceUpdateStorage?: boolean }
) => Promise<void>;
// Database type operations // Database type operations
updateDatabaseType: (databaseType: DatabaseType) => Promise<void>; updateDatabaseType: (databaseType: DatabaseType) => Promise<void>;
@@ -289,8 +293,7 @@ export const chartDBContext = createContext<ChartDBContext>({
areas: [], areas: [],
customTypes: [], customTypes: [],
schemas: [], schemas: [],
filteredSchemas: [], highlightCustomTypeId: emptyFn,
filterSchemas: emptyFn,
currentDiagram: { currentDiagram: {
id: '', id: '',
name: '', name: '',
@@ -308,6 +311,7 @@ export const chartDBContext = createContext<ChartDBContext>({
loadDiagramFromData: emptyFn, loadDiagramFromData: emptyFn,
clearDiagramData: emptyFn, clearDiagramData: emptyFn,
deleteDiagram: emptyFn, deleteDiagram: emptyFn,
updateDiagramData: emptyFn,
// Database type operations // Database type operations
updateDatabaseType: emptyFn, updateDatabaseType: emptyFn,

View File

@@ -1,12 +1,15 @@
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import type { DBTable } from '@/lib/domain/db-table'; import type { DBTable } from '@/lib/domain/db-table';
import { deepCopy, generateId } from '@/lib/utils'; import { deepCopy, generateId } from '@/lib/utils';
import { randomColor } from '@/lib/colors'; import { defaultTableColor, defaultAreaColor, viewColor } from '@/lib/colors';
import type { ChartDBContext, ChartDBEvent } from './chartdb-context'; import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
import { chartDBContext } from './chartdb-context'; import { chartDBContext } from './chartdb-context';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import type { DBField } from '@/lib/domain/db-field'; import type { DBField } from '@/lib/domain/db-field';
import type { DBIndex } from '@/lib/domain/db-index'; import {
getTableIndexesWithPrimaryKey,
type DBIndex,
} from '@/lib/domain/db-index';
import type { DBRelationship } from '@/lib/domain/db-relationship'; import type { DBRelationship } from '@/lib/domain/db-relationship';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack'; import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
@@ -17,7 +20,6 @@ import {
databasesWithSchemas, databasesWithSchemas,
schemaNameToSchemaId, schemaNameToSchemaId,
} from '@/lib/domain/db-schema'; } from '@/lib/domain/db-schema';
import { useLocalConfig } from '@/hooks/use-local-config';
import { defaultSchemas } from '@/lib/data/default-schemas'; import { defaultSchemas } from '@/lib/data/default-schemas';
import { useEventEmitter } from 'ahooks'; import { useEventEmitter } from 'ahooks';
import type { DBDependency } from '@/lib/domain/db-dependency'; import type { DBDependency } from '@/lib/domain/db-dependency';
@@ -39,11 +41,11 @@ export const ChartDBProvider: React.FC<
React.PropsWithChildren<ChartDBProviderProps> React.PropsWithChildren<ChartDBProviderProps>
> = ({ children, diagram, readonly: readonlyProp }) => { > = ({ children, diagram, readonly: readonlyProp }) => {
const { hasDiff } = useDiff(); const { hasDiff } = useDiff();
let db = useStorage(); const storageDB = useStorage();
const events = useEventEmitter<ChartDBEvent>(); const events = useEventEmitter<ChartDBEvent>();
const { setSchemasFilter, schemasFilter } = useLocalConfig();
const { addUndoAction, resetRedoStack, resetUndoStack } = const { addUndoAction, resetRedoStack, resetUndoStack } =
useRedoUndoStack(); useRedoUndoStack();
const [diagramId, setDiagramId] = useState(''); const [diagramId, setDiagramId] = useState('');
const [diagramName, setDiagramName] = useState(''); const [diagramName, setDiagramName] = useState('');
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date()); const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
@@ -65,8 +67,12 @@ export const ChartDBProvider: React.FC<
const [customTypes, setCustomTypes] = useState<DBCustomType[]>( const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
diagram?.customTypes ?? [] diagram?.customTypes ?? []
); );
const { events: diffEvents } = useDiff(); const { events: diffEvents } = useDiff();
const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
useState<string>();
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => { const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
const { tablesAdded, fieldsAdded, relationshipsAdded } = event.data; const { tablesAdded, fieldsAdded, relationshipsAdded } = event.data;
setTables((tables) => setTables((tables) =>
@@ -85,17 +91,16 @@ export const ChartDBProvider: React.FC<
diffEvents.useSubscription(diffCalculatedHandler); diffEvents.useSubscription(diffCalculatedHandler);
const defaultSchemaName = defaultSchemas[databaseType]; const defaultSchemaName = useMemo(
() => defaultSchemas[databaseType],
[databaseType]
);
const readonly = useMemo( const readonly = useMemo(
() => readonlyProp ?? hasDiff ?? false, () => readonlyProp ?? hasDiff ?? false,
[readonlyProp, hasDiff] [readonlyProp, hasDiff]
); );
if (readonly) {
db = storageInitialValue;
}
const schemas = useMemo( const schemas = useMemo(
() => () =>
databasesWithSchemas.includes(databaseType) databasesWithSchemas.includes(databaseType)
@@ -106,9 +111,11 @@ export const ChartDBProvider: React.FC<
.filter((schema) => !!schema) as string[] .filter((schema) => !!schema) as string[]
), ),
] ]
.sort((a, b) => .sort((a, b) => {
a === defaultSchemaName ? -1 : a.localeCompare(b) if (a === defaultSchemaName) return -1;
) if (b === defaultSchemaName) return 1;
return a.localeCompare(b);
})
.map( .map(
(schema): DBSchema => ({ (schema): DBSchema => ({
id: schemaNameToSchemaId(schema), id: schemaNameToSchemaId(schema),
@@ -122,34 +129,11 @@ export const ChartDBProvider: React.FC<
[tables, defaultSchemaName, databaseType] [tables, defaultSchemaName, databaseType]
); );
const filterSchemas: ChartDBContext['filterSchemas'] = useCallback( const db = useMemo(
(schemaIds) => { () => (readonly ? storageInitialValue : storageDB),
setSchemasFilter((prev) => ({ [storageDB, readonly]
...prev,
[diagramId]: schemaIds,
}));
},
[diagramId, setSchemasFilter]
); );
const filteredSchemas: ChartDBContext['filteredSchemas'] = useMemo(() => {
if (schemas.length === 0) {
return undefined;
}
const schemasFilterFromCache =
(schemasFilter[diagramId] ?? []).length === 0
? undefined // in case of empty filter, skip cache
: schemasFilter[diagramId];
return (
schemasFilterFromCache ?? [
schemas.find((s) => s.name === defaultSchemaName)?.id ??
schemas[0]?.id,
]
);
}, [schemasFilter, diagramId, schemas, defaultSchemaName]);
const currentDiagram: Diagram = useMemo( const currentDiagram: Diagram = useMemo(
() => ({ () => ({
id: diagramId, id: diagramId,
@@ -304,22 +288,27 @@ export const ChartDBProvider: React.FC<
); );
const addTables: ChartDBContext['addTables'] = useCallback( const addTables: ChartDBContext['addTables'] = useCallback(
async (tables: DBTable[], options = { updateHistory: true }) => { async (tablesToAdd: DBTable[], options = { updateHistory: true }) => {
setTables((currentTables) => [...currentTables, ...tables]); setTables((currentTables) => [...currentTables, ...tablesToAdd]);
const updatedAt = new Date(); const updatedAt = new Date();
setDiagramUpdatedAt(updatedAt); setDiagramUpdatedAt(updatedAt);
await Promise.all([ await Promise.all([
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }), db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
...tables.map((table) => db.addTable({ diagramId, table })), ...tablesToAdd.map((table) =>
db.addTable({ diagramId, table })
),
]); ]);
events.emit({ action: 'add_tables', data: { tables } }); events.emit({
action: 'add_tables',
data: { tables: tablesToAdd },
});
if (options.updateHistory) { if (options.updateHistory) {
addUndoAction({ addUndoAction({
action: 'addTables', action: 'addTables',
redoData: { tables }, redoData: { tables: tablesToAdd },
undoData: { tableIds: tables.map((t) => t.id) }, undoData: { tableIds: tablesToAdd.map((t) => t.id) },
}); });
resetRedoStack(); resetRedoStack();
} }
@@ -356,12 +345,17 @@ export const ChartDBProvider: React.FC<
}, },
], ],
indexes: [], indexes: [],
color: randomColor(), color: attributes?.isView ? viewColor : defaultTableColor,
createdAt: Date.now(), createdAt: Date.now(),
isView: false, isView: false,
order: tables.length, order: tables.length,
...attributes, ...attributes,
}; };
table.indexes = getTableIndexesWithPrimaryKey({
table,
});
await addTable(table); await addTable(table);
return table; return table;
@@ -653,17 +647,30 @@ export const ChartDBProvider: React.FC<
options = { updateHistory: true } options = { updateHistory: true }
) => { ) => {
const prevField = getField(tableId, fieldId); const prevField = getField(tableId, fieldId);
const updateTableFn = (table: DBTable) => {
const updatedTable: DBTable = {
...table,
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
} satisfies DBTable;
updatedTable.indexes = getTableIndexesWithPrimaryKey({
table: updatedTable,
});
return updatedTable;
};
setTables((tables) => setTables((tables) =>
tables.map((table) => tables.map((table) => {
table.id === tableId if (table.id === tableId) {
? { return updateTableFn(table);
...table, }
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f return table;
), })
}
: table
)
); );
const table = await db.getTable({ diagramId, id: tableId }); const table = await db.getTable({ diagramId, id: tableId });
@@ -678,10 +685,7 @@ export const ChartDBProvider: React.FC<
db.updateTable({ db.updateTable({
id: tableId, id: tableId,
attributes: { attributes: {
...table, ...updateTableFn(table),
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
}, },
}), }),
]); ]);
@@ -708,19 +712,29 @@ export const ChartDBProvider: React.FC<
fieldId: string, fieldId: string,
options = { updateHistory: true } options = { updateHistory: true }
) => { ) => {
const updateTableFn = (table: DBTable) => {
const updatedTable: DBTable = {
...table,
fields: table.fields.filter((f) => f.id !== fieldId),
} satisfies DBTable;
updatedTable.indexes = getTableIndexesWithPrimaryKey({
table: updatedTable,
});
return updatedTable;
};
const fields = getTable(tableId)?.fields ?? []; const fields = getTable(tableId)?.fields ?? [];
const prevField = getField(tableId, fieldId); const prevField = getField(tableId, fieldId);
setTables((tables) => setTables((tables) =>
tables.map((table) => tables.map((table) => {
table.id === tableId if (table.id === tableId) {
? { return updateTableFn(table);
...table, }
fields: table.fields.filter(
(f) => f.id !== fieldId return table;
), })
}
: table
)
); );
events.emit({ events.emit({
@@ -744,8 +758,7 @@ export const ChartDBProvider: React.FC<
db.updateTable({ db.updateTable({
id: tableId, id: tableId,
attributes: { attributes: {
...table, ...updateTableFn(table),
fields: table.fields.filter((f) => f.id !== fieldId),
}, },
}), }),
]); ]);
@@ -778,13 +791,23 @@ export const ChartDBProvider: React.FC<
options = { updateHistory: true } options = { updateHistory: true }
) => { ) => {
const fields = getTable(tableId)?.fields ?? []; const fields = getTable(tableId)?.fields ?? [];
setTables((tables) => setTables((tables) => {
tables.map((table) => return tables.map((table) => {
table.id === tableId if (table.id === tableId) {
? { ...table, fields: [...table.fields, field] } db.updateTable({
: table id: tableId,
) attributes: {
); ...table,
fields: [...table.fields, field],
},
});
return { ...table, fields: [...table.fields, field] };
}
return table;
});
});
events.emit({ events.emit({
action: 'add_field', action: 'add_field',
@@ -805,13 +828,6 @@ export const ChartDBProvider: React.FC<
setDiagramUpdatedAt(updatedAt); setDiagramUpdatedAt(updatedAt);
await Promise.all([ await Promise.all([
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }), db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
db.updateTable({
id: tableId,
attributes: {
...table,
fields: [...table.fields, field],
},
}),
]); ]);
if (options.updateHistory) { if (options.updateHistory) {
@@ -1098,12 +1114,15 @@ export const ChartDBProvider: React.FC<
const sourceFieldName = sourceField?.name ?? ''; const sourceFieldName = sourceField?.name ?? '';
const targetTable = getTable(targetTableId);
const targetTableSchema = targetTable?.schema;
const relationship: DBRelationship = { const relationship: DBRelationship = {
id: generateId(), id: generateId(),
name: `${sourceTableName}_${sourceFieldName}_fk`, name: `${sourceTableName}_${sourceFieldName}_fk`,
sourceSchema: sourceTable?.schema, sourceSchema: sourceTable?.schema,
sourceTableId, sourceTableId,
targetSchema: sourceTable?.schema, targetSchema: targetTableSchema,
targetTableId, targetTableId,
sourceFieldId, sourceFieldId,
targetFieldId, targetFieldId,
@@ -1425,7 +1444,7 @@ export const ChartDBProvider: React.FC<
y: 0, y: 0,
width: 300, width: 300,
height: 200, height: 200,
color: randomColor(), color: defaultAreaColor,
...attributes, ...attributes,
}; };
@@ -1508,22 +1527,37 @@ export const ChartDBProvider: React.FC<
[db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack] [db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack]
); );
const highlightCustomTypeId = useCallback(
(id?: string) => setHighlightedCustomTypeId(id),
[setHighlightedCustomTypeId]
);
const highlightedCustomType = useMemo(() => {
return highlightedCustomTypeId
? customTypes.find((type) => type.id === highlightedCustomTypeId)
: undefined;
}, [highlightedCustomTypeId, customTypes]);
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] = const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
useCallback( useCallback(
async (diagram) => { (diagram) => {
setDiagramId(diagram.id); setDiagramId(diagram.id);
setDiagramName(diagram.name); setDiagramName(diagram.name);
setDatabaseType(diagram.databaseType); setDatabaseType(diagram.databaseType);
setDatabaseEdition(diagram.databaseEdition); setDatabaseEdition(diagram.databaseEdition);
setTables(diagram?.tables ?? []); setTables(diagram.tables ?? []);
setRelationships(diagram?.relationships ?? []); setRelationships(diagram.relationships ?? []);
setDependencies(diagram?.dependencies ?? []); setDependencies(diagram.dependencies ?? []);
setAreas(diagram?.areas ?? []); setAreas(diagram.areas ?? []);
setCustomTypes(diagram?.customTypes ?? []); setCustomTypes(diagram.customTypes ?? []);
setDiagramCreatedAt(diagram.createdAt); setDiagramCreatedAt(diagram.createdAt);
setDiagramUpdatedAt(diagram.updatedAt); setDiagramUpdatedAt(diagram.updatedAt);
setHighlightedCustomTypeId(undefined);
events.emit({ action: 'load_diagram', data: { diagram } }); events.emit({ action: 'load_diagram', data: { diagram } });
resetRedoStack();
resetUndoStack();
}, },
[ [
setDiagramId, setDiagramId,
@@ -1537,13 +1571,26 @@ export const ChartDBProvider: React.FC<
setCustomTypes, setCustomTypes,
setDiagramCreatedAt, setDiagramCreatedAt,
setDiagramUpdatedAt, setDiagramUpdatedAt,
setHighlightedCustomTypeId,
events, events,
resetRedoStack,
resetUndoStack,
] ]
); );
const updateDiagramData: ChartDBContext['updateDiagramData'] = useCallback(
async (diagram, options) => {
const st = options?.forceUpdateStorage ? storageDB : db;
await st.deleteDiagram(diagram.id);
await st.addDiagram({ diagram });
loadDiagramFromData(diagram);
},
[db, storageDB, loadDiagramFromData]
);
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback( const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
async (diagramId: string) => { async (diagramId: string) => {
const diagram = await db.getDiagram(diagramId, { const diagram = await storageDB.getDiagram(diagramId, {
includeRelationships: true, includeRelationships: true,
includeTables: true, includeTables: true,
includeDependencies: true, includeDependencies: true,
@@ -1557,7 +1604,7 @@ export const ChartDBProvider: React.FC<
return diagram; return diagram;
}, },
[db, loadDiagramFromData] [storageDB, loadDiagramFromData]
); );
// Custom type operations // Custom type operations
@@ -1716,10 +1763,9 @@ export const ChartDBProvider: React.FC<
areas, areas,
currentDiagram, currentDiagram,
schemas, schemas,
filteredSchemas,
events, events,
readonly, readonly,
filterSchemas, updateDiagramData,
updateDiagramId, updateDiagramId,
updateDiagramName, updateDiagramName,
loadDiagram, loadDiagram,
@@ -1776,6 +1822,8 @@ export const ChartDBProvider: React.FC<
removeCustomType, removeCustomType,
removeCustomTypes, removeCustomTypes,
updateCustomType, updateCustomType,
highlightCustomTypeId,
highlightedCustomType,
}} }}
> >
{children} {children}

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { ConfigContext } from './config-context'; import { ConfigContext } from './config-context';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
@@ -8,7 +8,7 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
children, children,
}) => { }) => {
const { getConfig, updateConfig: updateDataConfig } = useStorage(); const { getConfig, updateConfig: updateDataConfig } = useStorage();
const [config, setConfig] = React.useState<ChartDBConfig | undefined>(); const [config, setConfig] = useState<ChartDBConfig | undefined>();
useEffect(() => { useEffect(() => {
const loadConfig = async () => { const loadConfig = async () => {
@@ -45,7 +45,12 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
}; };
return ( return (
<ConfigContext.Provider value={{ config, updateConfig }}> <ConfigContext.Provider
value={{
config,
updateConfig,
}}
>
{children} {children}
</ConfigContext.Provider> </ConfigContext.Provider>
); );

View File

@@ -0,0 +1,50 @@
import type { DBSchema } from '@/lib/domain';
import type {
DiagramFilter,
FilterTableInfo,
} from '@/lib/domain/diagram-filter/diagram-filter';
import { emptyFn } from '@/lib/utils';
import { createContext } from 'react';
export interface DiagramFilterContext {
filter?: DiagramFilter;
loading: boolean;
hasActiveFilter: boolean;
schemasDisplayed: DBSchema[];
clearSchemaIdsFilter: () => void;
clearTableIdsFilter: () => void;
setTableIdsFilterEmpty: () => void;
// reset
resetFilter: () => void;
toggleSchemaFilter: (schemaId: string) => void;
toggleTableFilter: (tableId: string) => void;
addSchemaToFilter: (schemaId: string) => void;
addTablesToFilter: (attrs: {
tableIds?: string[];
filterCallback?: (table: FilterTableInfo) => boolean;
}) => void;
removeTablesFromFilter: (attrs: {
tableIds?: string[];
filterCallback?: (table: FilterTableInfo) => boolean;
}) => void;
}
export const diagramFilterContext = createContext<DiagramFilterContext>({
hasActiveFilter: false,
clearSchemaIdsFilter: emptyFn,
clearTableIdsFilter: emptyFn,
setTableIdsFilterEmpty: emptyFn,
resetFilter: emptyFn,
toggleSchemaFilter: emptyFn,
toggleTableFilter: emptyFn,
addSchemaToFilter: emptyFn,
schemasDisplayed: [],
addTablesToFilter: emptyFn,
removeTablesFromFilter: emptyFn,
loading: false,
});

View File

@@ -0,0 +1,559 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import type { DiagramFilterContext } from './diagram-filter-context';
import { diagramFilterContext } from './diagram-filter-context';
import type {
DiagramFilter,
FilterTableInfo,
} from '@/lib/domain/diagram-filter/diagram-filter';
import {
reduceFilter,
spreadFilterTables,
} from '@/lib/domain/diagram-filter/diagram-filter';
import { useStorage } from '@/hooks/use-storage';
import { useChartDB } from '@/hooks/use-chartdb';
import { filterTable } from '@/lib/domain/diagram-filter/filter';
import { databasesWithSchemas, schemaNameToSchemaId } from '@/lib/domain';
import { defaultSchemas } from '@/lib/data/default-schemas';
import type { ChartDBEvent } from '../chartdb-context/chartdb-context';
export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const { diagramId, tables, schemas, databaseType, events } = useChartDB();
const { getDiagramFilter, updateDiagramFilter } = useStorage();
const [filter, setFilter] = useState<DiagramFilter>({});
const [loading, setLoading] = useState<boolean>(true);
const allSchemasIds = useMemo(() => {
return schemas.map((schema) => schema.id);
}, [schemas]);
const allTables: FilterTableInfo[] = useMemo(() => {
return tables.map(
(table) =>
({
id: table.id,
schemaId: table.schema
? schemaNameToSchemaId(table.schema)
: defaultSchemas[databaseType],
schema: table.schema ?? defaultSchemas[databaseType],
areaId: table.parentAreaId ?? undefined,
}) satisfies FilterTableInfo
);
}, [tables, databaseType]);
const diagramIdOfLoadedFilter = useRef<string | null>(null);
useEffect(() => {
if (diagramId && diagramId === diagramIdOfLoadedFilter.current) {
updateDiagramFilter(diagramId, filter);
}
}, [diagramId, filter, updateDiagramFilter]);
// Reset filter when diagram changes
useEffect(() => {
if (diagramIdOfLoadedFilter.current === diagramId) {
// If the diagramId hasn't changed, do not reset the filter
return;
}
setLoading(true);
const loadFilterFromStorage = async (diagramId: string) => {
if (diagramId) {
const storedFilter = await getDiagramFilter(diagramId);
let filterToSet = storedFilter;
if (!filterToSet) {
// If no filter is stored, set default based on database type
filterToSet =
schemas.length > 1
? { schemaIds: [schemas[0].id] }
: {};
}
setFilter(filterToSet);
}
setLoading(false);
};
setFilter({});
if (diagramId) {
loadFilterFromStorage(diagramId);
diagramIdOfLoadedFilter.current = diagramId;
}
}, [diagramId, getDiagramFilter, schemas]);
const clearSchemaIds: DiagramFilterContext['clearSchemaIdsFilter'] =
useCallback(() => {
setFilter(
(prev) =>
({
...prev,
schemaIds: undefined,
}) satisfies DiagramFilter
);
}, []);
const clearTableIds: DiagramFilterContext['clearTableIdsFilter'] =
useCallback(() => {
setFilter(
(prev) =>
({
...prev,
tableIds: undefined,
}) satisfies DiagramFilter
);
}, []);
const setTableIdsEmpty: DiagramFilterContext['setTableIdsFilterEmpty'] =
useCallback(() => {
setFilter(
(prev) =>
({
...prev,
tableIds: [],
}) satisfies DiagramFilter
);
}, []);
// Reset filter
const resetFilter: DiagramFilterContext['resetFilter'] = useCallback(() => {
setFilter({});
}, []);
const toggleSchemaFilter: DiagramFilterContext['toggleSchemaFilter'] =
useCallback(
(schemaId: string) => {
setFilter((prev) => {
const currentSchemaIds = prev.schemaIds;
// Check if schema is currently visible
const isSchemaVisible = !allTables.some(
(table) =>
table.schemaId === schemaId &&
filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter: prev,
options: {
defaultSchema: defaultSchemas[databaseType],
},
}) === false
);
let newSchemaIds: string[] | undefined;
let newTableIds: string[] | undefined = prev.tableIds;
if (isSchemaVisible) {
// Schema is visible, make it not visible
if (!currentSchemaIds) {
// All schemas are visible, create filter with all except this one
newSchemaIds = allSchemasIds.filter(
(id) => id !== schemaId
);
} else {
// Remove this schema from the filter
newSchemaIds = currentSchemaIds.filter(
(id) => id !== schemaId
);
}
// Remove tables from this schema from tableIds if present
if (prev.tableIds) {
const schemaTableIds = allTables
.filter((table) => table.schemaId === schemaId)
.map((table) => table.id);
newTableIds = prev.tableIds.filter(
(id) => !schemaTableIds.includes(id)
);
}
} else {
// Schema is not visible, make it visible
newSchemaIds = [
...new Set([...(currentSchemaIds || []), schemaId]),
];
// Add tables from this schema to tableIds if tableIds is defined
if (prev.tableIds) {
const schemaTableIds = allTables
.filter((table) => table.schemaId === schemaId)
.map((table) => table.id);
newTableIds = [
...new Set([
...prev.tableIds,
...schemaTableIds,
]),
];
}
}
// Use reduceFilter to optimize and handle edge cases
return reduceFilter(
{
schemaIds: newSchemaIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allSchemasIds, allTables, databaseType]
);
const toggleTableFilterForNoSchema = useCallback(
(tableId: string) => {
setFilter((prev) => {
const currentTableIds = prev.tableIds;
// Check if table is currently visible
const isTableVisible = filterTable({
table: { id: tableId, schema: undefined },
filter: prev,
options: { defaultSchema: undefined },
});
let newTableIds: string[] | undefined;
if (isTableVisible) {
// Table is visible, make it not visible
if (!currentTableIds) {
// All tables are visible, create filter with all except this one
newTableIds = allTables
.filter((t) => t.id !== tableId)
.map((t) => t.id);
} else {
// Remove this table from the filter
newTableIds = currentTableIds.filter(
(id) => id !== tableId
);
}
} else {
// Table is not visible, make it visible
newTableIds = [
...new Set([...(currentTableIds || []), tableId]),
];
}
// Use reduceFilter to optimize and handle edge cases
return reduceFilter(
{
schemaIds: undefined,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType]
);
const toggleTableFilter: DiagramFilterContext['toggleTableFilter'] =
useCallback(
(tableId: string) => {
if (!databasesWithSchemas.includes(databaseType)) {
// No schemas, toggle table filter without schema context
toggleTableFilterForNoSchema(tableId);
return;
}
setFilter((prev) => {
// Find the table in the tables list
const tableInfo = allTables.find((t) => t.id === tableId);
if (!tableInfo) {
return prev;
}
// Check if table is currently visible using filterTable
const isTableVisible = filterTable({
table: {
id: tableInfo.id,
schema: tableInfo.schema,
},
filter: prev,
options: {
defaultSchema: defaultSchemas[databaseType],
},
});
let newSchemaIds = prev.schemaIds;
let newTableIds = prev.tableIds;
if (isTableVisible) {
// Table is visible, make it not visible
// If the table is visible due to its schema being in schemaIds
if (
tableInfo?.schemaId &&
prev.schemaIds?.includes(tableInfo.schemaId)
) {
// Remove the schema from schemaIds and add all other tables from that schema to tableIds
newSchemaIds = prev.schemaIds.filter(
(id) => id !== tableInfo.schemaId
);
// Get all other tables from this schema (except the one being toggled)
const otherTablesFromSchema = allTables
.filter(
(t) =>
t.schemaId === tableInfo.schemaId &&
t.id !== tableId
)
.map((t) => t.id);
// Add these tables to tableIds
newTableIds = [
...(prev.tableIds || []),
...otherTablesFromSchema,
];
} else if (prev.tableIds?.includes(tableId)) {
// Table is visible because it's in tableIds, remove it
newTableIds = prev.tableIds.filter(
(id) => id !== tableId
);
} else if (!prev.tableIds && !prev.schemaIds) {
// No filters = all visible, create filter with all tables except this one
newTableIds = allTables
.filter((t) => t.id !== tableId)
.map((t) => t.id);
}
} else {
// Table is not visible, make it visible by adding to tableIds
newTableIds = [...(prev.tableIds || []), tableId];
}
// Use reduceFilter to optimize and handle edge cases
return reduceFilter(
{
schemaIds: newSchemaIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType, toggleTableFilterForNoSchema]
);
const addSchemaToFilter: DiagramFilterContext['addSchemaToFilter'] =
useCallback(
(schemaId: string) => {
setFilter((prev) => {
const currentSchemaIds = prev.schemaIds;
if (!currentSchemaIds) {
// No schemas are filtered
return prev;
}
// If schema is already filtered, do nothing
if (currentSchemaIds.includes(schemaId)) {
return prev;
}
// Add schema to the filter
const newSchemaIds = [...currentSchemaIds, schemaId];
if (newSchemaIds.length === allSchemasIds.length) {
// All schemas are now filtered, set to undefined
return {
...prev,
schemaIds: undefined,
} satisfies DiagramFilter;
}
return {
...prev,
schemaIds: newSchemaIds,
} satisfies DiagramFilter;
});
},
[allSchemasIds.length]
);
const hasActiveFilter: boolean = useMemo(() => {
return !!filter.schemaIds || !!filter.tableIds;
}, [filter]);
const schemasDisplayed: DiagramFilterContext['schemasDisplayed'] =
useMemo(() => {
if (!hasActiveFilter) {
return schemas;
}
const displayedSchemaIds = new Set<string>();
for (const table of allTables) {
if (
filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[databaseType],
},
})
) {
if (table.schemaId) {
displayedSchemaIds.add(table.schemaId);
}
}
}
return schemas.filter((schema) =>
displayedSchemaIds.has(schema.id)
);
}, [hasActiveFilter, schemas, allTables, filter, databaseType]);
const addTablesToFilter: DiagramFilterContext['addTablesToFilter'] =
useCallback(
({ tableIds, filterCallback }) => {
setFilter((prev) => {
let tableIdsToAdd: string[];
if (tableIds) {
// If tableIds are provided, use them directly
tableIdsToAdd = tableIds;
} else if (filterCallback) {
// If filterCallback is provided, filter tables based on it
tableIdsToAdd = allTables
.filter(filterCallback)
.map((table) => table.id);
} else {
// If neither is provided, do nothing
return prev;
}
const filterByTableIds = spreadFilterTables(
prev,
allTables satisfies FilterTableInfo[]
);
const currentTableIds = filterByTableIds.tableIds || [];
const newTableIds = [
...new Set([...currentTableIds, ...tableIdsToAdd]),
];
return reduceFilter(
{
...filterByTableIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType]
);
const removeTablesFromFilter: DiagramFilterContext['removeTablesFromFilter'] =
useCallback(
({ tableIds, filterCallback }) => {
setFilter((prev) => {
let tableIdsToRemovoe: string[];
if (tableIds) {
// If tableIds are provided, use them directly
tableIdsToRemovoe = tableIds;
} else if (filterCallback) {
// If filterCallback is provided, filter tables based on it
tableIdsToRemovoe = allTables
.filter(filterCallback)
.map((table) => table.id);
} else {
// If neither is provided, do nothing
return prev;
}
const filterByTableIds = spreadFilterTables(
prev,
allTables satisfies FilterTableInfo[]
);
const currentTableIds = filterByTableIds.tableIds || [];
const newTableIds = currentTableIds.filter(
(id) => !tableIdsToRemovoe.includes(id)
);
return reduceFilter(
{
...filterByTableIds,
tableIds: newTableIds,
},
allTables satisfies FilterTableInfo[],
{
databaseWithSchemas:
databasesWithSchemas.includes(databaseType),
}
);
});
},
[allTables, databaseType]
);
const eventConsumer = useCallback(
(event: ChartDBEvent) => {
if (!hasActiveFilter) {
return;
}
if (event.action === 'add_tables') {
addTablesToFilter({
tableIds: event.data.tables.map((table) => table.id),
});
}
},
[hasActiveFilter, addTablesToFilter]
);
events.useSubscription(eventConsumer);
const value: DiagramFilterContext = {
loading,
filter,
clearSchemaIdsFilter: clearSchemaIds,
setTableIdsFilterEmpty: setTableIdsEmpty,
clearTableIdsFilter: clearTableIds,
resetFilter,
toggleSchemaFilter,
toggleTableFilter,
addSchemaToFilter,
hasActiveFilter,
schemasDisplayed,
addTablesToFilter,
removeTablesFromFilter,
};
return (
<diagramFilterContext.Provider value={value}>
{children}
</diagramFilterContext.Provider>
);
};

View File

@@ -0,0 +1,4 @@
import { useContext } from 'react';
import { diagramFilterContext } from './diagram-filter-context';
export const useDiagramFilter = () => useContext(diagramFilterContext);

View File

@@ -32,14 +32,20 @@ export interface DiffContext {
originalDiagram: Diagram | null; originalDiagram: Diagram | null;
diffMap: DiffMap; diffMap: DiffMap;
hasDiff: boolean; hasDiff: boolean;
isSummaryOnly: boolean;
calculateDiff: ({ calculateDiff: ({
diagram, diagram,
newDiagram, newDiagram,
options,
}: { }: {
diagram: Diagram; diagram: Diagram;
newDiagram: Diagram; newDiagram: Diagram;
options?: {
summaryOnly?: boolean;
};
}) => void; }) => void;
resetDiff: () => void;
// table diff // table diff
checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean; checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean;
@@ -60,6 +66,15 @@ export interface DiffContext {
checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean; checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean;
getFieldNewName: ({ fieldId }: { fieldId: string }) => string | null; getFieldNewName: ({ fieldId }: { fieldId: string }) => string | null;
getFieldNewType: ({ fieldId }: { fieldId: string }) => DataType | null; getFieldNewType: ({ fieldId }: { fieldId: string }) => DataType | null;
getFieldNewPrimaryKey: ({ fieldId }: { fieldId: string }) => boolean | null;
getFieldNewNullable: ({ fieldId }: { fieldId: string }) => boolean | null;
getFieldNewCharacterMaximumLength: ({
fieldId,
}: {
fieldId: string;
}) => string | null;
getFieldNewScale: ({ fieldId }: { fieldId: string }) => number | null;
getFieldNewPrecision: ({ fieldId }: { fieldId: string }) => number | null;
// relationship diff // relationship diff
checkIfNewRelationship: ({ checkIfNewRelationship: ({

View File

@@ -6,7 +6,10 @@ import type {
} from './diff-context'; } from './diff-context';
import { diffContext } from './diff-context'; import { diffContext } from './diff-context';
import { generateDiff, getDiffMapKey } from './diff-check/diff-check'; import {
generateDiff,
getDiffMapKey,
} from '@/lib/domain/diff/diff-check/diff-check';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { useEventEmitter } from 'ahooks'; import { useEventEmitter } from 'ahooks';
import type { DBField } from '@/lib/domain/db-field'; import type { DBField } from '@/lib/domain/db-field';
@@ -29,6 +32,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const [fieldsChanged, setFieldsChanged] = React.useState< const [fieldsChanged, setFieldsChanged] = React.useState<
Map<string, boolean> Map<string, boolean>
>(new Map<string, boolean>()); >(new Map<string, boolean>());
const [isSummaryOnly, setIsSummaryOnly] = React.useState<boolean>(false);
const events = useEventEmitter<DiffEvent>(); const events = useEventEmitter<DiffEvent>();
@@ -124,7 +128,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
); );
const calculateDiff: DiffContext['calculateDiff'] = useCallback( const calculateDiff: DiffContext['calculateDiff'] = useCallback(
({ diagram, newDiagram: newDiagramArg }) => { ({ diagram, newDiagram: newDiagramArg, options }) => {
const { const {
diffMap: newDiffs, diffMap: newDiffs,
changedTables: newChangedTables, changedTables: newChangedTables,
@@ -136,6 +140,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
setFieldsChanged(newChangedFields); setFieldsChanged(newChangedFields);
setNewDiagram(newDiagramArg); setNewDiagram(newDiagramArg);
setOriginalDiagram(diagram); setOriginalDiagram(diagram);
setIsSummaryOnly(options?.summaryOnly ?? false);
events.emit({ events.emit({
action: 'diff_calculated', action: 'diff_calculated',
@@ -302,6 +307,117 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
[diffMap] [diffMap]
); );
const getFieldNewPrimaryKey = useCallback<
DiffContext['getFieldNewPrimaryKey']
>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'primaryKey',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return diff.newValue as boolean;
}
}
return null;
},
[diffMap]
);
const getFieldNewNullable = useCallback<DiffContext['getFieldNewNullable']>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'nullable',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return diff.newValue as boolean;
}
}
return null;
},
[diffMap]
);
const getFieldNewCharacterMaximumLength = useCallback<
DiffContext['getFieldNewCharacterMaximumLength']
>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'characterMaximumLength',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return diff.newValue as string;
}
}
return null;
},
[diffMap]
);
const getFieldNewScale = useCallback<DiffContext['getFieldNewScale']>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'scale',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return diff.newValue as number;
}
}
return null;
},
[diffMap]
);
const getFieldNewPrecision = useCallback<
DiffContext['getFieldNewPrecision']
>(
({ fieldId }) => {
const fieldKey = getDiffMapKey({
diffObject: 'field',
objectId: fieldId,
attribute: 'precision',
});
if (diffMap.has(fieldKey)) {
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return diff.newValue as number;
}
}
return null;
},
[diffMap]
);
const checkIfNewRelationship = useCallback< const checkIfNewRelationship = useCallback<
DiffContext['checkIfNewRelationship'] DiffContext['checkIfNewRelationship']
>( >(
@@ -336,6 +452,15 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
[diffMap] [diffMap]
); );
const resetDiff = useCallback<DiffContext['resetDiff']>(() => {
setDiffMap(new Map<string, ChartDBDiff>());
setTablesChanged(new Map<string, boolean>());
setFieldsChanged(new Map<string, boolean>());
setNewDiagram(null);
setOriginalDiagram(null);
setIsSummaryOnly(false);
}, []);
return ( return (
<diffContext.Provider <diffContext.Provider
value={{ value={{
@@ -343,8 +468,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
originalDiagram, originalDiagram,
diffMap, diffMap,
hasDiff: diffMap.size > 0, hasDiff: diffMap.size > 0,
isSummaryOnly,
calculateDiff, calculateDiff,
resetDiff,
// table diff // table diff
getTableNewName, getTableNewName,
@@ -359,6 +486,11 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
checkIfNewField, checkIfNewField,
getFieldNewName, getFieldNewName,
getFieldNewType, getFieldNewType,
getFieldNewPrimaryKey,
getFieldNewNullable,
getFieldNewCharacterMaximumLength,
getFieldNewScale,
getFieldNewPrecision,
// relationship diff // relationship diff
checkIfNewRelationship, checkIfNewRelationship,

View File

@@ -8,6 +8,7 @@ export enum KeyboardShortcutAction {
TOGGLE_SIDE_PANEL = 'toggle_side_panel', TOGGLE_SIDE_PANEL = 'toggle_side_panel',
SHOW_ALL = 'show_all', SHOW_ALL = 'show_all',
TOGGLE_THEME = 'toggle_theme', TOGGLE_THEME = 'toggle_theme',
TOGGLE_FILTER = 'toggle_filter',
} }
export interface KeyboardShortcut { export interface KeyboardShortcut {
@@ -71,6 +72,13 @@ export const keyboardShortcuts: Record<
keyCombinationMac: 'meta+m', keyCombinationMac: 'meta+m',
keyCombinationWin: 'ctrl+m', keyCombinationWin: 'ctrl+m',
}, },
[KeyboardShortcutAction.TOGGLE_FILTER]: {
action: KeyboardShortcutAction.TOGGLE_FILTER,
keyCombinationLabelMac: '⌘F',
keyCombinationLabelWin: 'Ctrl+F',
keyCombinationMac: 'meta+f',
keyCombinationWin: 'ctrl+f',
},
}; };
export interface KeyboardShortcutForOS { export interface KeyboardShortcutForOS {

View File

@@ -2,9 +2,9 @@ import { emptyFn } from '@/lib/utils';
import { createContext } from 'react'; import { createContext } from 'react';
export type SidebarSection = export type SidebarSection =
| 'dbml'
| 'tables' | 'tables'
| 'relationships' | 'refs'
| 'dependencies'
| 'areas' | 'areas'
| 'customTypes'; | 'customTypes';
@@ -13,14 +13,16 @@ export interface LayoutContext {
openTableFromSidebar: (tableId: string) => void; openTableFromSidebar: (tableId: string) => void;
closeAllTablesInSidebar: () => void; closeAllTablesInSidebar: () => void;
openedRelationshipInSidebar: string | undefined;
openRelationshipFromSidebar: (relationshipId: string) => void; openRelationshipFromSidebar: (relationshipId: string) => void;
closeAllRelationshipsInSidebar: () => void; closeAllRelationshipsInSidebar: () => void;
openedDependencyInSidebar: string | undefined;
openDependencyFromSidebar: (dependencyId: string) => void; openDependencyFromSidebar: (dependencyId: string) => void;
closeAllDependenciesInSidebar: () => void; closeAllDependenciesInSidebar: () => void;
openedRefInSidebar: string | undefined;
openRefFromSidebar: (refId: string) => void;
closeAllRefsInSidebar: () => void;
openedAreaInSidebar: string | undefined; openedAreaInSidebar: string | undefined;
openAreaFromSidebar: (areaId: string) => void; openAreaFromSidebar: (areaId: string) => void;
closeAllAreasInSidebar: () => void; closeAllAreasInSidebar: () => void;
@@ -36,24 +38,22 @@ export interface LayoutContext {
hideSidePanel: () => void; hideSidePanel: () => void;
showSidePanel: () => void; showSidePanel: () => void;
toggleSidePanel: () => void; toggleSidePanel: () => void;
isSelectSchemaOpen: boolean;
openSelectSchema: () => void;
closeSelectSchema: () => void;
} }
export const layoutContext = createContext<LayoutContext>({ export const layoutContext = createContext<LayoutContext>({
openedTableInSidebar: undefined, openedTableInSidebar: undefined,
selectedSidebarSection: 'tables', selectedSidebarSection: 'tables',
openedRelationshipInSidebar: undefined,
openRelationshipFromSidebar: emptyFn, openRelationshipFromSidebar: emptyFn,
closeAllRelationshipsInSidebar: emptyFn, closeAllRelationshipsInSidebar: emptyFn,
openedDependencyInSidebar: undefined,
openDependencyFromSidebar: emptyFn, openDependencyFromSidebar: emptyFn,
closeAllDependenciesInSidebar: emptyFn, closeAllDependenciesInSidebar: emptyFn,
openedRefInSidebar: undefined,
openRefFromSidebar: emptyFn,
closeAllRefsInSidebar: emptyFn,
openedAreaInSidebar: undefined, openedAreaInSidebar: undefined,
openAreaFromSidebar: emptyFn, openAreaFromSidebar: emptyFn,
closeAllAreasInSidebar: emptyFn, closeAllAreasInSidebar: emptyFn,
@@ -70,8 +70,4 @@ export const layoutContext = createContext<LayoutContext>({
hideSidePanel: emptyFn, hideSidePanel: emptyFn,
showSidePanel: emptyFn, showSidePanel: emptyFn,
toggleSidePanel: emptyFn, toggleSidePanel: emptyFn,
isSelectSchemaOpen: false,
openSelectSchema: emptyFn,
closeSelectSchema: emptyFn,
}); });

View File

@@ -10,10 +10,9 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState< const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState<
string | undefined string | undefined
>(); >();
const [openedRelationshipInSidebar, setOpenedRelationshipInSidebar] = const [openedRefInSidebar, setOpenedRefInSidebar] = React.useState<
React.useState<string | undefined>(); string | undefined
const [openedDependencyInSidebar, setOpenedDependencyInSidebar] = >();
React.useState<string | undefined>();
const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState< const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState<
string | undefined string | undefined
>(); >();
@@ -23,17 +22,18 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
React.useState<SidebarSection>('tables'); React.useState<SidebarSection>('tables');
const [isSidePanelShowed, setIsSidePanelShowed] = const [isSidePanelShowed, setIsSidePanelShowed] =
React.useState<boolean>(isDesktop); React.useState<boolean>(isDesktop);
const [isSelectSchemaOpen, setIsSelectSchemaOpen] =
React.useState<boolean>(false);
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] = const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
() => setOpenedTableInSidebar(''); () => setOpenedTableInSidebar('');
const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] = const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] =
() => setOpenedRelationshipInSidebar(''); () => setOpenedRefInSidebar('');
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] = const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
() => setOpenedDependencyInSidebar(''); () => setOpenedRefInSidebar('');
const closeAllRefsInSidebar: LayoutContext['closeAllRefsInSidebar'] = () =>
setOpenedRefInSidebar('');
const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] = const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] =
() => setOpenedAreaInSidebar(''); () => setOpenedAreaInSidebar('');
@@ -62,17 +62,23 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] = const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] =
(relationshipId) => { (relationshipId) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('relationships'); setSelectedSidebarSection('refs');
setOpenedRelationshipInSidebar(relationshipId); setOpenedRefInSidebar(relationshipId);
}; };
const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] = const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] =
(dependencyId) => { (dependencyId) => {
showSidePanel(); showSidePanel();
setSelectedSidebarSection('dependencies'); setSelectedSidebarSection('refs');
setOpenedDependencyInSidebar(dependencyId); setOpenedRefInSidebar(dependencyId);
}; };
const openRefFromSidebar: LayoutContext['openRefFromSidebar'] = (refId) => {
showSidePanel();
setSelectedSidebarSection('refs');
setOpenedRefInSidebar(refId);
};
const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = ( const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = (
areaId areaId
) => { ) => {
@@ -88,11 +94,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
setOpenedTableInSidebar(customTypeId); setOpenedTableInSidebar(customTypeId);
}; };
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
setIsSelectSchemaOpen(true);
const closeSelectSchema: LayoutContext['closeSelectSchema'] = () =>
setIsSelectSchemaOpen(false);
return ( return (
<layoutContext.Provider <layoutContext.Provider
value={{ value={{
@@ -100,7 +101,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
selectedSidebarSection, selectedSidebarSection,
openTableFromSidebar, openTableFromSidebar,
selectSidebarSection: setSelectedSidebarSection, selectSidebarSection: setSelectedSidebarSection,
openedRelationshipInSidebar,
openRelationshipFromSidebar, openRelationshipFromSidebar,
closeAllTablesInSidebar, closeAllTablesInSidebar,
closeAllRelationshipsInSidebar, closeAllRelationshipsInSidebar,
@@ -108,12 +108,11 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
hideSidePanel, hideSidePanel,
showSidePanel, showSidePanel,
toggleSidePanel, toggleSidePanel,
isSelectSchemaOpen,
openSelectSchema,
closeSelectSchema,
openedDependencyInSidebar,
openDependencyFromSidebar, openDependencyFromSidebar,
closeAllDependenciesInSidebar, closeAllDependenciesInSidebar,
openedRefInSidebar,
openRefFromSidebar,
closeAllRefsInSidebar,
openedAreaInSidebar, openedAreaInSidebar,
openAreaFromSidebar, openAreaFromSidebar,
closeAllAreasInSidebar, closeAllAreasInSidebar,

View File

@@ -4,8 +4,6 @@ import type { Theme } from '../theme-context/theme-context';
export type ScrollAction = 'pan' | 'zoom'; export type ScrollAction = 'pan' | 'zoom';
export type SchemasFilter = Record<string, string[]>;
export interface LocalConfigContext { export interface LocalConfigContext {
theme: Theme; theme: Theme;
setTheme: (theme: Theme) => void; setTheme: (theme: Theme) => void;
@@ -13,16 +11,14 @@ export interface LocalConfigContext {
scrollAction: ScrollAction; scrollAction: ScrollAction;
setScrollAction: (action: ScrollAction) => void; setScrollAction: (action: ScrollAction) => void;
schemasFilter: SchemasFilter; showDBViews: boolean;
setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>; setShowDBViews: (showViews: boolean) => void;
showCardinality: boolean; showCardinality: boolean;
setShowCardinality: (showCardinality: boolean) => void; setShowCardinality: (showCardinality: boolean) => void;
hideMultiSchemaNotification: boolean; showFieldAttributes: boolean;
setHideMultiSchemaNotification: ( setShowFieldAttributes: (showFieldAttributes: boolean) => void;
hideMultiSchemaNotification: boolean
) => void;
githubRepoOpened: boolean; githubRepoOpened: boolean;
setGithubRepoOpened: (githubRepoOpened: boolean) => void; setGithubRepoOpened: (githubRepoOpened: boolean) => void;
@@ -30,9 +26,6 @@ export interface LocalConfigContext {
starUsDialogLastOpen: number; starUsDialogLastOpen: number;
setStarUsDialogLastOpen: (lastOpen: number) => void; setStarUsDialogLastOpen: (lastOpen: number) => void;
showDependenciesOnCanvas: boolean;
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
showMiniMapOnCanvas: boolean; showMiniMapOnCanvas: boolean;
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void; setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
} }
@@ -44,14 +37,14 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
scrollAction: 'pan', scrollAction: 'pan',
setScrollAction: emptyFn, setScrollAction: emptyFn,
schemasFilter: {}, showDBViews: false,
setSchemasFilter: emptyFn, setShowDBViews: emptyFn,
showCardinality: true, showCardinality: true,
setShowCardinality: emptyFn, setShowCardinality: emptyFn,
hideMultiSchemaNotification: false, showFieldAttributes: true,
setHideMultiSchemaNotification: emptyFn, setShowFieldAttributes: emptyFn,
githubRepoOpened: false, githubRepoOpened: false,
setGithubRepoOpened: emptyFn, setGithubRepoOpened: emptyFn,
@@ -59,9 +52,6 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
starUsDialogLastOpen: 0, starUsDialogLastOpen: 0,
setStarUsDialogLastOpen: emptyFn, setStarUsDialogLastOpen: emptyFn,
showDependenciesOnCanvas: false,
setShowDependenciesOnCanvas: emptyFn,
showMiniMapOnCanvas: false, showMiniMapOnCanvas: false,
setShowMiniMapOnCanvas: emptyFn, setShowMiniMapOnCanvas: emptyFn,
}); });

View File

@@ -1,17 +1,16 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import type { SchemasFilter, ScrollAction } from './local-config-context'; import type { ScrollAction } from './local-config-context';
import { LocalConfigContext } from './local-config-context'; import { LocalConfigContext } from './local-config-context';
import type { Theme } from '../theme-context/theme-context'; import type { Theme } from '../theme-context/theme-context';
const themeKey = 'theme'; const themeKey = 'theme';
const scrollActionKey = 'scroll_action'; const scrollActionKey = 'scroll_action';
const schemasFilterKey = 'schemas_filter';
const showCardinalityKey = 'show_cardinality'; const showCardinalityKey = 'show_cardinality';
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification'; const showFieldAttributesKey = 'show_field_attributes';
const githubRepoOpenedKey = 'github_repo_opened'; const githubRepoOpenedKey = 'github_repo_opened';
const starUsDialogLastOpenKey = 'star_us_dialog_last_open'; const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
const showMiniMapOnCanvasKey = 'show_minimap_on_canvas'; const showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
const showDBViewsKey = 'show_db_views';
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
children, children,
@@ -24,20 +23,17 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan' (localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
); );
const [schemasFilter, setSchemasFilter] = React.useState<SchemasFilter>( const [showDBViews, setShowDBViews] = React.useState<boolean>(
JSON.parse( (localStorage.getItem(showDBViewsKey) || 'false') === 'true'
localStorage.getItem(schemasFilterKey) || '{}'
) as SchemasFilter
); );
const [showCardinality, setShowCardinality] = React.useState<boolean>( const [showCardinality, setShowCardinality] = React.useState<boolean>(
(localStorage.getItem(showCardinalityKey) || 'true') === 'true' (localStorage.getItem(showCardinalityKey) || 'true') === 'true'
); );
const [hideMultiSchemaNotification, setHideMultiSchemaNotification] = const [showFieldAttributes, setShowFieldAttributes] =
React.useState<boolean>( React.useState<boolean>(
(localStorage.getItem(hideMultiSchemaNotificationKey) || (localStorage.getItem(showFieldAttributesKey) || 'true') === 'true'
'false') === 'true'
); );
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>( const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
@@ -49,12 +45,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0') parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
); );
const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] =
React.useState<boolean>(
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
'true'
);
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] = const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
React.useState<boolean>( React.useState<boolean>(
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true' (localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
@@ -71,13 +61,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString()); localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
}, [githubRepoOpened]); }, [githubRepoOpened]);
useEffect(() => {
localStorage.setItem(
hideMultiSchemaNotificationKey,
hideMultiSchemaNotification.toString()
);
}, [hideMultiSchemaNotification]);
useEffect(() => { useEffect(() => {
localStorage.setItem(themeKey, theme); localStorage.setItem(themeKey, theme);
}, [theme]); }, [theme]);
@@ -87,20 +70,13 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
}, [scrollAction]); }, [scrollAction]);
useEffect(() => { useEffect(() => {
localStorage.setItem(schemasFilterKey, JSON.stringify(schemasFilter)); localStorage.setItem(showDBViewsKey, showDBViews.toString());
}, [schemasFilter]); }, [showDBViews]);
useEffect(() => { useEffect(() => {
localStorage.setItem(showCardinalityKey, showCardinality.toString()); localStorage.setItem(showCardinalityKey, showCardinality.toString());
}, [showCardinality]); }, [showCardinality]);
useEffect(() => {
localStorage.setItem(
showDependenciesOnCanvasKey,
showDependenciesOnCanvas.toString()
);
}, [showDependenciesOnCanvas]);
useEffect(() => { useEffect(() => {
localStorage.setItem( localStorage.setItem(
showMiniMapOnCanvasKey, showMiniMapOnCanvasKey,
@@ -115,18 +91,16 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
setTheme, setTheme,
scrollAction, scrollAction,
setScrollAction, setScrollAction,
schemasFilter, showDBViews,
setSchemasFilter, setShowDBViews,
showCardinality, showCardinality,
setShowCardinality, setShowCardinality,
hideMultiSchemaNotification, showFieldAttributes,
setHideMultiSchemaNotification, setShowFieldAttributes,
setGithubRepoOpened, setGithubRepoOpened,
githubRepoOpened, githubRepoOpened,
starUsDialogLastOpen, starUsDialogLastOpen,
setStarUsDialogLastOpen, setStarUsDialogLastOpen,
showDependenciesOnCanvas,
setShowDependenciesOnCanvas,
showMiniMapOnCanvas, showMiniMapOnCanvas,
setShowMiniMapOnCanvas, setShowMiniMapOnCanvas,
}} }}

View File

@@ -7,12 +7,21 @@ import type { ChartDBConfig } from '@/lib/domain/config';
import type { DBDependency } from '@/lib/domain/db-dependency'; import type { DBDependency } from '@/lib/domain/db-dependency';
import type { Area } from '@/lib/domain/area'; import type { Area } from '@/lib/domain/area';
import type { DBCustomType } from '@/lib/domain/db-custom-type'; import type { DBCustomType } from '@/lib/domain/db-custom-type';
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
export interface StorageContext { export interface StorageContext {
// Config operations // Config operations
getConfig: () => Promise<ChartDBConfig | undefined>; getConfig: () => Promise<ChartDBConfig | undefined>;
updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>; updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>;
// Diagram filter operations
getDiagramFilter: (diagramId: string) => Promise<DiagramFilter | undefined>;
updateDiagramFilter: (
diagramId: string,
filter: DiagramFilter
) => Promise<void>;
deleteDiagramFilter: (diagramId: string) => Promise<void>;
// Diagram operations // Diagram operations
addDiagram: (params: { diagram: Diagram }) => Promise<void>; addDiagram: (params: { diagram: Diagram }) => Promise<void>;
listDiagrams: (options?: { listDiagrams: (options?: {
@@ -132,6 +141,10 @@ export const storageInitialValue: StorageContext = {
getConfig: emptyFn, getConfig: emptyFn,
updateConfig: emptyFn, updateConfig: emptyFn,
getDiagramFilter: emptyFn,
updateDiagramFilter: emptyFn,
deleteDiagramFilter: emptyFn,
addDiagram: emptyFn, addDiagram: emptyFn,
listDiagrams: emptyFn, listDiagrams: emptyFn,
getDiagram: emptyFn, getDiagram: emptyFn,

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
import { createContext } from 'react'; import { createContext } from 'react';
import { emptyFn } from '@/lib/utils'; import { emptyFn } from '@/lib/utils';
import type { Theme, EffectiveTheme } from '@/lib/types';
export type Theme = 'light' | 'dark' | 'system'; export type { Theme, EffectiveTheme };
export type EffectiveTheme = Exclude<Theme, 'system'>;
export interface ThemeContext { export interface ThemeContext {
theme: Theme; theme: Theme;

View File

@@ -48,6 +48,7 @@ export const ThemeProvider: React.FC<React.PropsWithChildren> = ({
handleThemeToggle, handleThemeToggle,
{ {
preventDefault: true, preventDefault: true,
enableOnFormTags: true,
}, },
[handleThemeToggle] [handleThemeToggle]
); );

View File

@@ -35,7 +35,22 @@ import type { OnChange } from '@monaco-editor/react';
import { useDebounce } from '@/hooks/use-debounce-v2'; import { useDebounce } from '@/hooks/use-debounce-v2';
import { InstructionsSection } from './instructions-section/instructions-section'; import { InstructionsSection } from './instructions-section/instructions-section';
import { parseSQLError } from '@/lib/data/sql-import'; import { parseSQLError } from '@/lib/data/sql-import';
import type { editor } from 'monaco-editor'; import type { editor, IDisposable } from 'monaco-editor';
import { waitFor } from '@/lib/utils';
import {
validateSQL,
type ValidationResult,
} from '@/lib/data/sql-import/sql-validator';
import { SQLValidationStatus } from './sql-validation-status';
const calculateContentSizeMB = (content: string): number => {
return content.length / (1024 * 1024); // Convert to MB
};
const calculateIsLargeFile = (content: string): boolean => {
const contentSizeMB = calculateContentSizeMB(content);
return contentSizeMB > 2; // Consider large if over 2MB
};
const errorScriptOutputMessage = const errorScriptOutputMessage =
'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.'; 'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.';
@@ -117,6 +132,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const { effectiveTheme } = useTheme(); const { effectiveTheme } = useTheme();
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null); const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const pasteDisposableRef = useRef<IDisposable | null>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const { isSm: isDesktop } = useBreakpoint('sm'); const { isSm: isDesktop } = useBreakpoint('sm');
@@ -124,6 +140,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const [showCheckJsonButton, setShowCheckJsonButton] = useState(false); const [showCheckJsonButton, setShowCheckJsonButton] = useState(false);
const [isCheckingJson, setIsCheckingJson] = useState(false); const [isCheckingJson, setIsCheckingJson] = useState(false);
const [showSSMSInfoDialog, setShowSSMSInfoDialog] = useState(false); const [showSSMSInfoDialog, setShowSSMSInfoDialog] = useState(false);
const [sqlValidation, setSqlValidation] = useState<ValidationResult | null>(
null
);
const [isAutoFixing, setIsAutoFixing] = useState(false);
const [showAutoFixButton, setShowAutoFixButton] = useState(false);
useEffect(() => { useEffect(() => {
setScriptResult(''); setScriptResult('');
@@ -134,11 +155,33 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
// Check if the ddl is valid // Check if the ddl is valid
useEffect(() => { useEffect(() => {
if (importMethod !== 'ddl') { if (importMethod !== 'ddl') {
setSqlValidation(null);
setShowAutoFixButton(false);
return; return;
} }
if (!scriptResult.trim()) return; if (!scriptResult.trim()) {
setSqlValidation(null);
setShowAutoFixButton(false);
return;
}
// First run our validation based on database type
const validation = validateSQL(scriptResult, databaseType);
setSqlValidation(validation);
// If we have auto-fixable errors, show the auto-fix button
if (validation.fixedSQL && validation.errors.length > 0) {
setShowAutoFixButton(true);
// Don't try to parse invalid SQL
setErrorMessage('SQL contains syntax errors');
return;
}
// Hide auto-fix button if no fixes available
setShowAutoFixButton(false);
// Validate the SQL (either original or already fixed)
parseSQLError({ parseSQLError({
sqlContent: scriptResult, sqlContent: scriptResult,
sourceDatabaseType: databaseType, sourceDatabaseType: databaseType,
@@ -184,8 +227,44 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
} }
}, [errorMessage.length, onImport, scriptResult]); }, [errorMessage.length, onImport, scriptResult]);
const handleAutoFix = useCallback(() => {
if (sqlValidation?.fixedSQL) {
setIsAutoFixing(true);
setShowAutoFixButton(false);
setErrorMessage('');
// Apply the fix with a delay so user sees the fixing message
setTimeout(() => {
setScriptResult(sqlValidation.fixedSQL!);
setTimeout(() => {
setIsAutoFixing(false);
}, 100);
}, 1000);
}
}, [sqlValidation, setScriptResult]);
const handleErrorClick = useCallback((line: number) => {
if (editorRef.current) {
// Set cursor to the error line
editorRef.current.setPosition({ lineNumber: line, column: 1 });
editorRef.current.revealLineInCenter(line);
editorRef.current.focus();
}
}, []);
const formatEditor = useCallback(() => { const formatEditor = useCallback(() => {
if (editorRef.current) { if (editorRef.current) {
const model = editorRef.current.getModel();
if (model) {
const content = model.getValue();
// Skip formatting for large files (> 2MB)
if (calculateIsLargeFile(content)) {
return;
}
}
setTimeout(() => { setTimeout(() => {
editorRef.current editorRef.current
?.getAction('editor.action.formatDocument') ?.getAction('editor.action.formatDocument')
@@ -211,7 +290,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const handleCheckJson = useCallback(async () => { const handleCheckJson = useCallback(async () => {
setIsCheckingJson(true); setIsCheckingJson(true);
const fixedJson = await fixMetadataJson(scriptResult); await waitFor(1000);
const fixedJson = fixMetadataJson(scriptResult);
if (isStringMetadataJson(fixedJson)) { if (isStringMetadataJson(fixedJson)) {
setScriptResult(fixedJson); setScriptResult(fixedJson);
@@ -227,37 +307,69 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
setIsCheckingJson(false); setIsCheckingJson(false);
}, [scriptResult, setScriptResult, formatEditor]); }, [scriptResult, setScriptResult, formatEditor]);
const detectAndSetImportMethod = useCallback(() => {
const content = editorRef.current?.getValue();
if (content && content.trim()) {
const detectedType = detectContentType(content);
if (detectedType && detectedType !== importMethod) {
setImportMethod(detectedType);
}
}
}, [setImportMethod, importMethod]);
const [editorDidMount, setEditorDidMount] = useState(false);
useEffect(() => { useEffect(() => {
if (editorRef.current && editorDidMount) { // Cleanup paste handler on unmount
editorRef.current.onDidPaste(() => { return () => {
setTimeout(() => { if (pasteDisposableRef.current) {
editorRef.current pasteDisposableRef.current.dispose();
?.getAction('editor.action.formatDocument') pasteDisposableRef.current = null;
?.run(); }
}, 0); };
setTimeout(detectAndSetImportMethod, 0); }, []);
});
}
}, [detectAndSetImportMethod, editorDidMount]);
const handleEditorDidMount = useCallback( const handleEditorDidMount = useCallback(
(editor: editor.IStandaloneCodeEditor) => { (editor: editor.IStandaloneCodeEditor) => {
editorRef.current = editor; editorRef.current = editor;
setEditorDidMount(true);
// Cleanup previous disposable if it exists
if (pasteDisposableRef.current) {
pasteDisposableRef.current.dispose();
pasteDisposableRef.current = null;
}
// Add paste handler for all modes
const disposable = editor.onDidPaste(() => {
const model = editor.getModel();
if (!model) return;
const content = model.getValue();
// Skip formatting for large files (> 2MB) to prevent browser freezing
const isLargeFile = calculateIsLargeFile(content);
// First, detect content type to determine if we should switch modes
const detectedType = detectContentType(content);
if (detectedType && detectedType !== importMethod) {
// Switch to the detected mode immediately
setImportMethod(detectedType);
// Only format if it's JSON (query mode) AND file is not too large
if (detectedType === 'query' && !isLargeFile) {
// For JSON mode, format after a short delay
setTimeout(() => {
editor
.getAction('editor.action.formatDocument')
?.run();
}, 100);
}
// For DDL mode, do NOT format as it can break the SQL
} else {
// Content type didn't change, apply formatting based on current mode
if (importMethod === 'query' && !isLargeFile) {
// Only format JSON content if not too large
setTimeout(() => {
editor
.getAction('editor.action.formatDocument')
?.run();
}, 100);
}
// For DDL mode or large files, do NOT format
}
});
pasteDisposableRef.current = disposable;
}, },
[] [importMethod, setImportMethod]
); );
const renderHeader = useCallback(() => { const renderHeader = useCallback(() => {
@@ -314,7 +426,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
: 'dbml-light' : 'dbml-light'
} }
options={{ options={{
formatOnPaste: true, formatOnPaste: false, // Never format on paste - we handle it manually
minimap: { enabled: false }, minimap: { enabled: false },
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
automaticLayout: true, automaticLayout: true,
@@ -343,10 +455,13 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
</Suspense> </Suspense>
</div> </div>
{errorMessage ? ( {errorMessage || (importMethod === 'ddl' && sqlValidation) ? (
<div className="mt-2 flex shrink-0 items-center gap-2"> <SQLValidationStatus
<p className="text-xs text-red-700">{errorMessage}</p> validation={sqlValidation}
</div> errorMessage={errorMessage}
isAutoFixing={isAutoFixing}
onErrorClick={handleErrorClick}
/>
) : null} ) : null}
</div> </div>
), ),
@@ -357,6 +472,9 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
effectiveTheme, effectiveTheme,
debouncedHandleInputChange, debouncedHandleInputChange,
handleEditorDidMount, handleEditorDidMount,
sqlValidation,
isAutoFixing,
handleErrorClick,
] ]
); );
@@ -442,13 +560,28 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
) )
)} )}
</Button> </Button>
) : showAutoFixButton && importMethod === 'ddl' ? (
<Button
type="button"
variant="secondary"
onClick={handleAutoFix}
disabled={isAutoFixing}
className="bg-sky-600 text-white hover:bg-sky-700"
>
{isAutoFixing ? (
<Spinner size="small" />
) : (
'Try auto-fix'
)}
</Button>
) : keepDialogAfterImport ? ( ) : keepDialogAfterImport ? (
<Button <Button
type="button" type="button"
variant="default" variant="default"
disabled={ disabled={
scriptResult.trim().length === 0 || scriptResult.trim().length === 0 ||
errorMessage.length > 0 errorMessage.length > 0 ||
isAutoFixing
} }
onClick={handleImport} onClick={handleImport}
> >
@@ -461,7 +594,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
variant="default" variant="default"
disabled={ disabled={
scriptResult.trim().length === 0 || scriptResult.trim().length === 0 ||
errorMessage.length > 0 errorMessage.length > 0 ||
isAutoFixing
} }
onClick={handleImport} onClick={handleImport}
> >
@@ -494,6 +628,10 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
handleCheckJson, handleCheckJson,
goBack, goBack,
t, t,
importMethod,
isAutoFixing,
showAutoFixButton,
handleAutoFix,
]); ]);
return ( return (

View File

@@ -0,0 +1,179 @@
import React, { useMemo } from 'react';
import { CheckCircle, AlertTriangle, MessageCircleWarning } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/alert/alert';
import type { ValidationResult } from '@/lib/data/sql-import/sql-validator';
import { Separator } from '@/components/separator/separator';
import { ScrollArea } from '@/components/scroll-area/scroll-area';
import { Spinner } from '@/components/spinner/spinner';
interface SQLValidationStatusProps {
validation?: ValidationResult | null;
errorMessage: string;
isAutoFixing?: boolean;
onErrorClick?: (line: number) => void;
}
export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
validation,
errorMessage,
isAutoFixing = false,
onErrorClick,
}) => {
const hasErrors = useMemo(
() => validation?.errors.length && validation.errors.length > 0,
[validation?.errors]
);
const hasWarnings = useMemo(
() => validation?.warnings && validation.warnings.length > 0,
[validation?.warnings]
);
const wasAutoFixed = useMemo(
() =>
validation?.warnings?.some((w) =>
w.message.includes('Auto-fixed')
) || false,
[validation?.warnings]
);
if (!validation && !errorMessage && !isAutoFixing) return null;
if (isAutoFixing) {
return (
<>
<Separator className="mb-1 mt-2" />
<div className="rounded-md border border-sky-200 bg-sky-50 dark:border-sky-800 dark:bg-sky-950">
<div className="space-y-3 p-3 pt-2 text-sky-700 dark:text-sky-300">
<div className="flex items-start gap-2">
<Spinner className="mt-0.5 size-4 shrink-0 text-sky-700 dark:text-sky-300" />
<div className="flex-1 text-sm text-sky-700 dark:text-sky-300">
Auto-fixing SQL syntax errors...
</div>
</div>
</div>
</div>
</>
);
}
// If we have parser errors (errorMessage) after validation
if (errorMessage && !hasErrors) {
return (
<>
<Separator className="mb-1 mt-2" />
<div className="mb-1 flex shrink-0 items-center gap-2">
<p className="text-xs text-red-700">{errorMessage}</p>
</div>
</>
);
}
return (
<>
<Separator className="mb-1 mt-2" />
{hasErrors ? (
<div className="rounded-md border border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950">
<ScrollArea className="h-24">
<div className="space-y-3 p-3 pt-2 text-red-700 dark:text-red-300">
{validation?.errors
.slice(0, 3)
.map((error, idx) => (
<div
key={idx}
className="flex items-start gap-2"
>
<MessageCircleWarning className="mt-0.5 size-4 shrink-0 text-red-700 dark:text-red-300" />
<div className="flex-1 text-sm text-red-700 dark:text-red-300">
<button
onClick={() =>
onErrorClick?.(error.line)
}
className="rounded font-medium underline hover:text-red-600 focus:outline-none focus:ring-1 focus:ring-red-500 dark:hover:text-red-200"
type="button"
>
Line {error.line}
</button>
<span className="mx-1">:</span>
<span className="text-xs">
{error.message}
</span>
{error.suggestion && (
<div className="mt-1 flex items-start gap-2">
<span className="text-xs font-medium ">
{error.suggestion}
</span>
</div>
)}
</div>
</div>
))}
{validation?.errors &&
validation?.errors.length > 3 ? (
<div className="flex items-center gap-2">
<MessageCircleWarning className="mt-0.5 size-4 shrink-0 text-red-700 dark:text-red-300" />
<span className="text-xs font-medium">
{validation.errors.length - 3} more
error
{validation.errors.length - 3 > 1
? 's'
: ''}
</span>
</div>
) : null}
</div>
</ScrollArea>
</div>
) : null}
{wasAutoFixed && !hasErrors ? (
<Alert className="border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950">
<CheckCircle className="size-4 text-green-600 dark:text-green-400" />
<AlertDescription className="text-sm text-green-700 dark:text-green-300">
SQL syntax errors were automatically fixed. Your SQL is
now ready to import.
</AlertDescription>
</Alert>
) : null}
{hasWarnings && !hasErrors ? (
<div className="rounded-md border border-sky-200 bg-sky-50 dark:border-sky-800 dark:bg-sky-950">
<ScrollArea className="h-24">
<div className="space-y-3 p-3 pt-2 text-sky-700 dark:text-sky-300">
<div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-sky-700 dark:text-sky-300" />
<div className="flex-1 text-sm text-sky-700 dark:text-sky-300">
<div className="mb-1 font-medium">
Import Info:
</div>
{validation?.warnings.map(
(warning, idx) => (
<div
key={idx}
className="ml-2 text-xs"
>
{warning.message}
</div>
)
)}
</div>
</div>
</div>
</ScrollArea>
</div>
) : null}
{!hasErrors && !hasWarnings && !errorMessage && validation ? (
<div className="rounded-md border border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950">
<div className="space-y-3 p-3 pt-2 text-green-700 dark:text-green-300">
<div className="flex items-start gap-2">
<CheckCircle className="mt-0.5 size-4 shrink-0 text-green-700 dark:text-green-300" />
<div className="flex-1 text-sm text-green-700 dark:text-green-300">
SQL syntax validated successfully
</div>
</div>
</div>
</div>
) : null}
</>
);
};

View File

@@ -0,0 +1,2 @@
export const MAX_TABLES_IN_DIAGRAM = 500;
export const MAX_TABLES_WITHOUT_SHOWING_FILTER = 50;

View File

@@ -0,0 +1,683 @@
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { Button } from '@/components/button/button';
import { Input } from '@/components/input/input';
import { Search, AlertCircle, Check, X, View, Table } from 'lucide-react';
import { Checkbox } from '@/components/checkbox/checkbox';
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
import { schemaNameToDomainSchemaName } from '@/lib/domain/db-schema';
import { cn } from '@/lib/utils';
import {
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import type { SelectedTable } from '@/lib/data/import-metadata/filter-metadata';
import { generateTableKey } from '@/lib/domain';
import { Spinner } from '@/components/spinner/spinner';
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationPrevious,
PaginationNext,
} from '@/components/pagination/pagination';
import { MAX_TABLES_IN_DIAGRAM } from './constants';
import { useBreakpoint } from '@/hooks/use-breakpoint';
import { useTranslation } from 'react-i18next';
export interface SelectTablesProps {
databaseMetadata?: DatabaseMetadata;
onImport: ({
selectedTables,
databaseMetadata,
}: {
selectedTables?: SelectedTable[];
databaseMetadata?: DatabaseMetadata;
}) => Promise<void>;
onBack: () => void;
isLoading?: boolean;
}
const TABLES_PER_PAGE = 10;
interface TableInfo {
key: string;
schema?: string;
tableName: string;
fullName: string;
type: 'table' | 'view';
}
export const SelectTables: React.FC<SelectTablesProps> = ({
databaseMetadata,
onImport,
onBack,
isLoading = false,
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [showTables, setShowTables] = useState(true);
const [showViews, setShowViews] = useState(false);
const { t } = useTranslation();
const [isImporting, setIsImporting] = useState(false);
// Prepare all tables and views with their metadata
const allTables = useMemo(() => {
const tables: TableInfo[] = [];
// Add regular tables
databaseMetadata?.tables.forEach((table) => {
const schema = schemaNameToDomainSchemaName(table.schema);
const tableName = table.table;
const key = `table:${generateTableKey({ tableName, schemaName: schema })}`;
tables.push({
key,
schema,
tableName,
fullName: schema ? `${schema}.${tableName}` : tableName,
type: 'table',
});
});
// Add views
databaseMetadata?.views?.forEach((view) => {
const schema = schemaNameToDomainSchemaName(view.schema);
const viewName = view.view_name;
if (!viewName) {
return;
}
const key = `view:${generateTableKey({
tableName: viewName,
schemaName: schema,
})}`;
tables.push({
key,
schema,
tableName: viewName,
fullName:
schema === 'default' ? viewName : `${schema}.${viewName}`,
type: 'view',
});
});
return tables.sort((a, b) => a.fullName.localeCompare(b.fullName));
}, [databaseMetadata?.tables, databaseMetadata?.views]);
// Count tables and views separately
const tableCount = useMemo(
() => allTables.filter((t) => t.type === 'table').length,
[allTables]
);
const viewCount = useMemo(
() => allTables.filter((t) => t.type === 'view').length,
[allTables]
);
// Initialize selectedTables with all tables (not views) if less than 100 tables
const [selectedTables, setSelectedTables] = useState<Set<string>>(() => {
const tables = allTables.filter((t) => t.type === 'table');
if (tables.length < MAX_TABLES_IN_DIAGRAM) {
return new Set(tables.map((t) => t.key));
}
return new Set();
});
// Filter tables based on search term and type filters
const filteredTables = useMemo(() => {
let filtered = allTables;
// Filter by type
filtered = filtered.filter((table) => {
if (table.type === 'table' && !showTables) return false;
if (table.type === 'view' && !showViews) return false;
return true;
});
// Filter by search term
if (searchTerm.trim()) {
const searchLower = searchTerm.toLowerCase();
filtered = filtered.filter(
(table) =>
table.tableName.toLowerCase().includes(searchLower) ||
table.schema?.toLowerCase().includes(searchLower) ||
table.fullName.toLowerCase().includes(searchLower)
);
}
return filtered;
}, [allTables, searchTerm, showTables, showViews]);
// Calculate pagination
const totalPages = useMemo(
() => Math.max(1, Math.ceil(filteredTables.length / TABLES_PER_PAGE)),
[filteredTables.length]
);
const paginatedTables = useMemo(() => {
const startIndex = (currentPage - 1) * TABLES_PER_PAGE;
const endIndex = startIndex + TABLES_PER_PAGE;
return filteredTables.slice(startIndex, endIndex);
}, [filteredTables, currentPage]);
// Get currently visible selected tables
const visibleSelectedTables = useMemo(() => {
return paginatedTables.filter((table) => selectedTables.has(table.key));
}, [paginatedTables, selectedTables]);
const canAddMore = useMemo(
() => selectedTables.size < MAX_TABLES_IN_DIAGRAM,
[selectedTables.size]
);
const hasSearchResults = useMemo(
() => filteredTables.length > 0,
[filteredTables.length]
);
const allVisibleSelected = useMemo(
() =>
visibleSelectedTables.length === paginatedTables.length &&
paginatedTables.length > 0,
[visibleSelectedTables.length, paginatedTables.length]
);
const canSelectAllFiltered = useMemo(
() =>
filteredTables.length > 0 &&
filteredTables.some((table) => !selectedTables.has(table.key)) &&
canAddMore,
[filteredTables, selectedTables, canAddMore]
);
// Reset to first page when search changes
useEffect(() => {
setCurrentPage(1);
}, [searchTerm]);
const handleTableToggle = useCallback(
(tableKey: string) => {
const newSelected = new Set(selectedTables);
if (newSelected.has(tableKey)) {
newSelected.delete(tableKey);
} else if (selectedTables.size < MAX_TABLES_IN_DIAGRAM) {
newSelected.add(tableKey);
}
setSelectedTables(newSelected);
},
[selectedTables]
);
const handleTogglePageSelection = useCallback(() => {
const newSelected = new Set(selectedTables);
if (allVisibleSelected) {
// Deselect all on current page
for (const table of paginatedTables) {
newSelected.delete(table.key);
}
} else {
// Select all on current page
for (const table of paginatedTables) {
if (newSelected.size >= MAX_TABLES_IN_DIAGRAM) break;
newSelected.add(table.key);
}
}
setSelectedTables(newSelected);
}, [allVisibleSelected, paginatedTables, selectedTables]);
const handleSelectAllFiltered = useCallback(() => {
const newSelected = new Set(selectedTables);
for (const table of filteredTables) {
if (newSelected.size >= MAX_TABLES_IN_DIAGRAM) break;
newSelected.add(table.key);
}
setSelectedTables(newSelected);
}, [filteredTables, selectedTables]);
const handleNextPage = useCallback(() => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
}, [currentPage, totalPages]);
const handlePrevPage = useCallback(() => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
}, [currentPage]);
const handleClearSelection = useCallback(() => {
setSelectedTables(new Set());
}, []);
const handleConfirm = useCallback(async () => {
if (isImporting) {
return;
}
setIsImporting(true);
try {
const selectedTableObjects: SelectedTable[] = Array.from(
selectedTables
)
.map((key): SelectedTable | null => {
const table = allTables.find((t) => t.key === key);
if (!table) return null;
return {
schema: table.schema,
table: table.tableName,
type: table.type,
} satisfies SelectedTable;
})
.filter((t): t is SelectedTable => t !== null);
await onImport({
selectedTables: selectedTableObjects,
databaseMetadata,
});
} finally {
setIsImporting(false);
}
}, [selectedTables, allTables, onImport, databaseMetadata, isImporting]);
const { isMd: isDesktop } = useBreakpoint('md');
const renderPagination = useCallback(
() => (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={handlePrevPage}
className={cn(
'cursor-pointer',
currentPage === 1 &&
'pointer-events-none opacity-50'
)}
/>
</PaginationItem>
<PaginationItem>
<span className="px-3 text-sm text-muted-foreground">
Page {currentPage} of {totalPages}
</span>
</PaginationItem>
<PaginationItem>
<PaginationNext
onClick={handleNextPage}
className={cn(
'cursor-pointer',
(currentPage >= totalPages ||
filteredTables.length === 0) &&
'pointer-events-none opacity-50'
)}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
),
[
currentPage,
totalPages,
handlePrevPage,
handleNextPage,
filteredTables.length,
]
);
if (isLoading) {
return (
<div className="flex h-[400px] items-center justify-center">
<div className="text-center">
<Spinner className="mb-4" />
<p className="text-sm text-muted-foreground">
Parsing database metadata...
</p>
</div>
</div>
);
}
return (
<>
<DialogHeader>
<DialogTitle>Select Tables to Import</DialogTitle>
<DialogDescription>
{tableCount} {tableCount === 1 ? 'table' : 'tables'}
{viewCount > 0 && (
<>
{' and '}
{viewCount} {viewCount === 1 ? 'view' : 'views'}
</>
)}
{' found. '}
{allTables.length > MAX_TABLES_IN_DIAGRAM
? `Select up to ${MAX_TABLES_IN_DIAGRAM} to import.`
: 'Choose which ones to import.'}
</DialogDescription>
</DialogHeader>
<DialogInternalContent>
<div className="flex h-full flex-col space-y-4">
{/* Warning/Info Banner */}
{allTables.length > MAX_TABLES_IN_DIAGRAM ? (
<div
className={cn(
'flex items-center gap-2 rounded-lg p-3 text-sm',
'bg-amber-50 text-amber-800 dark:bg-amber-950 dark:text-amber-200'
)}
>
<AlertCircle className="size-4 shrink-0" />
<span>
Due to performance limitations, you can import a
maximum of {MAX_TABLES_IN_DIAGRAM} tables.
</span>
</div>
) : null}
{/* Search Input */}
<div className="relative">
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search tables..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="px-9"
/>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<X className="size-4" />
</button>
)}
</div>
{/* Selection Status and Actions - Responsive layout */}
<div className="flex flex-col items-center gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
{/* Left side: selection count -> checkboxes -> results found */}
<div className="flex flex-col items-center gap-3 text-sm sm:flex-row sm:items-center sm:gap-4">
<div className="flex flex-col items-center gap-1 sm:flex-row sm:items-center sm:gap-4">
<span className="text-center font-medium">
{selectedTables.size} /{' '}
{Math.min(
MAX_TABLES_IN_DIAGRAM,
allTables.length
)}{' '}
items selected
</span>
</div>
<div className="flex items-center gap-3 sm:border-x sm:px-4">
<div className="flex items-center gap-2">
<Checkbox
checked={showTables}
onCheckedChange={(checked) => {
// Prevent unchecking if it's the only one checked
if (!checked && !showViews) return;
setShowTables(!!checked);
}}
/>
<Table
className="size-4"
strokeWidth={1.5}
/>
<span>tables</span>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={showViews}
onCheckedChange={(checked) => {
// Prevent unchecking if it's the only one checked
if (!checked && !showTables) return;
setShowViews(!!checked);
}}
/>
<View
className="size-4"
strokeWidth={1.5}
/>
<span>views</span>
</div>
</div>
<span className="hidden text-muted-foreground sm:inline">
{filteredTables.length}{' '}
{filteredTables.length === 1
? 'result'
: 'results'}{' '}
found
</span>
</div>
{/* Right side: action buttons */}
<div className="flex flex-wrap items-center justify-center gap-2">
{hasSearchResults && (
<>
{/* Show page selection button when not searching and no selection */}
{!searchTerm &&
selectedTables.size === 0 && (
<Button
variant="outline"
size="sm"
onClick={
handleTogglePageSelection
}
disabled={
paginatedTables.length === 0
}
>
{allVisibleSelected
? 'Deselect'
: 'Select'}{' '}
page
</Button>
)}
{/* Show Select all button when there are unselected tables */}
{canSelectAllFiltered &&
selectedTables.size === 0 && (
<Button
variant="outline"
size="sm"
onClick={
handleSelectAllFiltered
}
disabled={!canSelectAllFiltered}
title={(() => {
const unselectedCount =
filteredTables.filter(
(table) =>
!selectedTables.has(
table.key
)
).length;
const remainingCapacity =
MAX_TABLES_IN_DIAGRAM -
selectedTables.size;
if (
unselectedCount >
remainingCapacity
) {
return `Can only select ${remainingCapacity} more tables (${MAX_TABLES_IN_DIAGRAM} max limit)`;
}
return undefined;
})()}
>
{(() => {
const unselectedCount =
filteredTables.filter(
(table) =>
!selectedTables.has(
table.key
)
).length;
const remainingCapacity =
MAX_TABLES_IN_DIAGRAM -
selectedTables.size;
if (
unselectedCount >
remainingCapacity
) {
return `Select ${remainingCapacity} of ${unselectedCount}`;
}
return `Select all ${unselectedCount}`;
})()}
</Button>
)}
</>
)}
{selectedTables.size > 0 && (
<>
{/* Show page selection/deselection button when user has selections */}
{paginatedTables.length > 0 && (
<Button
variant="outline"
size="sm"
onClick={handleTogglePageSelection}
>
{allVisibleSelected
? 'Deselect'
: 'Select'}{' '}
page
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={handleClearSelection}
>
Clear selection
</Button>
</>
)}
</div>
</div>
</div>
{/* Table List */}
<div className="flex min-h-[428px] flex-1 flex-col">
{hasSearchResults ? (
<>
<div className="flex-1 py-4">
<div className="space-y-1">
{paginatedTables.map((table) => {
const isSelected = selectedTables.has(
table.key
);
const isDisabled =
!isSelected &&
selectedTables.size >=
MAX_TABLES_IN_DIAGRAM;
return (
<div
key={table.key}
className={cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
{
'cursor-not-allowed':
isDisabled,
'bg-muted hover:bg-muted/80':
isSelected,
'hover:bg-accent':
!isSelected &&
!isDisabled,
}
)}
>
<Checkbox
checked={isSelected}
disabled={isDisabled}
onCheckedChange={() =>
handleTableToggle(
table.key
)
}
/>
{table.type === 'view' ? (
<View
className="size-4"
strokeWidth={1.5}
/>
) : (
<Table
className="size-4"
strokeWidth={1.5}
/>
)}
<span className="flex-1">
{table.schema ? (
<span className="text-muted-foreground">
{table.schema}.
</span>
) : null}
<span className="font-medium">
{table.tableName}
</span>
{table.type === 'view' && (
<span className="ml-2 text-xs text-muted-foreground">
(view)
</span>
)}
</span>
{isSelected && (
<Check className="size-4 text-pink-600" />
)}
</div>
);
})}
</div>
</div>
</>
) : (
<div className="flex h-full items-center justify-center py-4">
<p className="text-sm text-muted-foreground">
{searchTerm
? 'No tables found matching your search.'
: 'Start typing to search for tables...'}
</p>
</div>
)}
</div>
{isDesktop ? renderPagination() : null}
</DialogInternalContent>
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2 md:justify-between md:gap-0">
<Button
type="button"
variant="secondary"
onClick={onBack}
disabled={isImporting}
>
{t('new_diagram_dialog.back')}
</Button>
<Button
onClick={handleConfirm}
disabled={selectedTables.size === 0 || isImporting}
className="bg-pink-500 text-white hover:bg-pink-600"
>
{isImporting ? (
<>
<Spinner className="mr-2 size-4 text-white" />
Importing...
</>
) : (
`Import ${selectedTables.size} Tables`
)}
</Button>
{!isDesktop ? renderPagination() : null}
</DialogFooter>
</>
);
};

View File

@@ -1,4 +1,5 @@
export enum CreateDiagramDialogStep { export enum CreateDiagramDialogStep {
SELECT_DATABASE = 'SELECT_DATABASE', SELECT_DATABASE = 'SELECT_DATABASE',
IMPORT_DATABASE = 'IMPORT_DATABASE', IMPORT_DATABASE = 'IMPORT_DATABASE',
SELECT_TABLES = 'SELECT_TABLES',
} }

View File

@@ -3,7 +3,7 @@ import { Dialog, DialogContent } from '@/components/dialog/dialog';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram'; import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useConfig } from '@/hooks/use-config'; import { useConfig } from '@/hooks/use-config';
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata'; import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
@@ -15,9 +15,13 @@ import type { DatabaseEdition } from '@/lib/domain/database-edition';
import { SelectDatabase } from './select-database/select-database'; import { SelectDatabase } from './select-database/select-database';
import { CreateDiagramDialogStep } from './create-diagram-dialog-step'; import { CreateDiagramDialogStep } from './create-diagram-dialog-step';
import { ImportDatabase } from '../common/import-database/import-database'; import { ImportDatabase } from '../common/import-database/import-database';
import { SelectTables } from '../common/select-tables/select-tables';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
import { sqlImportToDiagram } from '@/lib/data/sql-import'; import { sqlImportToDiagram } from '@/lib/data/sql-import';
import type { SelectedTable } from '@/lib/data/import-metadata/filter-metadata';
import { filterMetadataByTables } from '@/lib/data/import-metadata/filter-metadata';
import { MAX_TABLES_WITHOUT_SHOWING_FILTER } from '../common/select-tables/constants';
export interface CreateDiagramDialogProps extends BaseDialogProps {} export interface CreateDiagramDialogProps extends BaseDialogProps {}
@@ -42,6 +46,8 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
const { listDiagrams, addDiagram } = useStorage(); const { listDiagrams, addDiagram } = useStorage();
const [diagramNumber, setDiagramNumber] = useState<number>(1); const [diagramNumber, setDiagramNumber] = useState<number>(1);
const navigate = useNavigate(); const navigate = useNavigate();
const [parsedMetadata, setParsedMetadata] = useState<DatabaseMetadata>();
const [isParsingMetadata, setIsParsingMetadata] = useState(false);
useEffect(() => { useEffect(() => {
setDatabaseEdition(undefined); setDatabaseEdition(undefined);
@@ -62,49 +68,72 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
setDatabaseEdition(undefined); setDatabaseEdition(undefined);
setScriptResult(''); setScriptResult('');
setImportMethod('query'); setImportMethod('query');
setParsedMetadata(undefined);
}, [dialog.open]); }, [dialog.open]);
const hasExistingDiagram = (diagramId ?? '').trim().length !== 0; const hasExistingDiagram = (diagramId ?? '').trim().length !== 0;
const importNewDiagram = useCallback(async () => { const importNewDiagram = useCallback(
let diagram: Diagram | undefined; async ({
selectedTables,
databaseMetadata,
}: {
selectedTables?: SelectedTable[];
databaseMetadata?: DatabaseMetadata;
} = {}) => {
let diagram: Diagram | undefined;
if (importMethod === 'ddl') { if (importMethod === 'ddl') {
diagram = await sqlImportToDiagram({ diagram = await sqlImportToDiagram({
sqlContent: scriptResult, sqlContent: scriptResult,
sourceDatabaseType: databaseType, sourceDatabaseType: databaseType,
targetDatabaseType: databaseType, targetDatabaseType: databaseType,
});
} else {
let metadata: DatabaseMetadata | undefined = databaseMetadata;
if (!metadata) {
metadata = loadDatabaseMetadata(scriptResult);
}
if (selectedTables && selectedTables.length > 0) {
metadata = filterMetadataByTables({
metadata,
selectedTables,
});
}
diagram = await loadFromDatabaseMetadata({
databaseType,
databaseMetadata: metadata,
diagramNumber,
databaseEdition:
databaseEdition?.trim().length === 0
? undefined
: databaseEdition,
});
}
await addDiagram({ diagram });
await updateConfig({
config: { defaultDiagramId: diagram.id },
}); });
} else {
const databaseMetadata: DatabaseMetadata =
loadDatabaseMetadata(scriptResult);
diagram = await loadFromDatabaseMetadata({ closeCreateDiagramDialog();
databaseType, navigate(`/diagrams/${diagram.id}`);
databaseMetadata, },
diagramNumber, [
databaseEdition: importMethod,
databaseEdition?.trim().length === 0 databaseType,
? undefined addDiagram,
: databaseEdition, databaseEdition,
}); closeCreateDiagramDialog,
} navigate,
updateConfig,
await addDiagram({ diagram }); scriptResult,
await updateConfig({ config: { defaultDiagramId: diagram.id } }); diagramNumber,
closeCreateDiagramDialog(); ]
navigate(`/diagrams/${diagram.id}`); );
}, [
importMethod,
databaseType,
addDiagram,
databaseEdition,
closeCreateDiagramDialog,
navigate,
updateConfig,
scriptResult,
diagramNumber,
]);
const createEmptyDiagram = useCallback(async () => { const createEmptyDiagram = useCallback(async () => {
const diagram: Diagram = { const diagram: Diagram = {
@@ -138,10 +167,56 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
openImportDBMLDialog, openImportDBMLDialog,
]); ]);
const importNewDiagramOrFilterTables = useCallback(async () => {
try {
setIsParsingMetadata(true);
if (importMethod === 'ddl') {
await importNewDiagram();
} else {
// Parse metadata asynchronously to avoid blocking the UI
const metadata = await new Promise<DatabaseMetadata>(
(resolve, reject) => {
setTimeout(() => {
try {
const result =
loadDatabaseMetadata(scriptResult);
resolve(result);
} catch (err) {
reject(err);
}
}, 0);
}
);
const totalTablesAndViews =
metadata.tables.length + (metadata.views?.length || 0);
setParsedMetadata(metadata);
// Check if it's a large database that needs table selection
if (totalTablesAndViews > MAX_TABLES_WITHOUT_SHOWING_FILTER) {
setStep(CreateDiagramDialogStep.SELECT_TABLES);
} else {
await importNewDiagram({
databaseMetadata: metadata,
});
}
}
} finally {
setIsParsingMetadata(false);
}
}, [importMethod, scriptResult, importNewDiagram]);
return ( return (
<Dialog <Dialog
{...dialog} {...dialog}
onOpenChange={(open) => { onOpenChange={(open) => {
// Don't allow closing while parsing metadata
if (isParsingMetadata) {
return;
}
if (!hasExistingDiagram) { if (!hasExistingDiagram) {
return; return;
} }
@@ -154,6 +229,8 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
<DialogContent <DialogContent
className="flex max-h-dvh w-full flex-col md:max-w-[900px]" className="flex max-h-dvh w-full flex-col md:max-w-[900px]"
showClose={hasExistingDiagram} showClose={hasExistingDiagram}
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
> >
{step === CreateDiagramDialogStep.SELECT_DATABASE ? ( {step === CreateDiagramDialogStep.SELECT_DATABASE ? (
<SelectDatabase <SelectDatabase
@@ -165,9 +242,9 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
setStep(CreateDiagramDialogStep.IMPORT_DATABASE) setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
} }
/> />
) : ( ) : step === CreateDiagramDialogStep.IMPORT_DATABASE ? (
<ImportDatabase <ImportDatabase
onImport={importNewDiagram} onImport={importNewDiagramOrFilterTables}
onCreateEmptyDiagram={createEmptyDiagram} onCreateEmptyDiagram={createEmptyDiagram}
databaseEdition={databaseEdition} databaseEdition={databaseEdition}
databaseType={databaseType} databaseType={databaseType}
@@ -180,8 +257,18 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
title={t('new_diagram_dialog.import_database.title')} title={t('new_diagram_dialog.import_database.title')}
importMethod={importMethod} importMethod={importMethod}
setImportMethod={setImportMethod} setImportMethod={setImportMethod}
keepDialogAfterImport={true}
/> />
)} ) : step === CreateDiagramDialogStep.SELECT_TABLES ? (
<SelectTables
isLoading={isParsingMetadata || !parsedMetadata}
databaseMetadata={parsedMetadata}
onImport={importNewDiagram}
onBack={() =>
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
}
/>
) : null}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@@ -69,6 +69,7 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
type="button" type="button"
variant="outline" variant="outline"
onClick={createNewDiagram} onClick={createNewDiagram}
disabled={databaseType === DatabaseType.GENERIC}
> >
{t('new_diagram_dialog.empty_diagram')} {t('new_diagram_dialog.empty_diagram')}
</Button> </Button>

View File

@@ -218,8 +218,14 @@ export const CreateRelationshipDialog: React.FC<
closeCreateRelationshipDialog(); closeCreateRelationshipDialog();
} }
}} }}
modal={false}
> >
<DialogContent className="flex flex-col overflow-y-auto" showClose> <DialogContent
className="flex flex-col overflow-y-auto"
showClose
forceOverlay
onInteractOutside={(e) => e.preventDefault()}
>
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{t('create_relationship_dialog.title')} {t('create_relationship_dialog.title')}

View File

@@ -17,15 +17,21 @@ import { useDialog } from '@/hooks/use-dialog';
import { import {
exportBaseSQL, exportBaseSQL,
exportSQL, exportSQL,
} from '@/lib/data/export-metadata/export-sql-script'; } from '@/lib/data/sql-export/export-sql-script';
import { databaseTypeToLabelMap } from '@/lib/databases'; import { databaseTypeToLabelMap } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
import { Annoyed, Sparkles } from 'lucide-react'; import { Annoyed, Sparkles } from 'lucide-react';
import React, { useCallback, useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
import {
filterDependency,
filterRelationship,
filterTable,
} from '@/lib/domain/diagram-filter/filter';
import { defaultSchemas } from '@/lib/data/default-schemas';
export interface ExportSQLDialogProps extends BaseDialogProps { export interface ExportSQLDialogProps extends BaseDialogProps {
targetDatabaseType: DatabaseType; targetDatabaseType: DatabaseType;
@@ -36,7 +42,8 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
targetDatabaseType, targetDatabaseType,
}) => { }) => {
const { closeExportSQLDialog } = useDialog(); const { closeExportSQLDialog } = useDialog();
const { currentDiagram, filteredSchemas } = useChartDB(); const { currentDiagram } = useChartDB();
const { filter } = useDiagramFilter();
const { t } = useTranslation(); const { t } = useTranslation();
const [script, setScript] = React.useState<string>(); const [script, setScript] = React.useState<string>();
const [error, setError] = React.useState<boolean>(false); const [error, setError] = React.useState<boolean>(false);
@@ -48,7 +55,16 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const filteredDiagram: Diagram = { const filteredDiagram: Diagram = {
...currentDiagram, ...currentDiagram,
tables: currentDiagram.tables?.filter((table) => tables: currentDiagram.tables?.filter((table) =>
shouldShowTablesBySchemaFilter(table, filteredSchemas) filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
), ),
relationships: currentDiagram.relationships?.filter((rel) => { relationships: currentDiagram.relationships?.filter((rel) => {
const sourceTable = currentDiagram.tables?.find( const sourceTable = currentDiagram.tables?.find(
@@ -60,11 +76,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return ( return (
sourceTable && sourceTable &&
targetTable && targetTable &&
shouldShowTablesBySchemaFilter( filterRelationship({
sourceTable, tableA: {
filteredSchemas id: sourceTable.id,
) && schema: sourceTable.schema,
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas) },
tableB: {
id: targetTable.id,
schema: targetTable.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
); );
}), }),
dependencies: currentDiagram.dependencies?.filter((dep) => { dependencies: currentDiagram.dependencies?.filter((dep) => {
@@ -77,11 +102,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return ( return (
table && table &&
dependentTable && dependentTable &&
shouldShowTablesBySchemaFilter(table, filteredSchemas) && filterDependency({
shouldShowTablesBySchemaFilter( tableA: {
dependentTable, id: table.id,
filteredSchemas schema: table.schema,
) },
tableB: {
id: dependentTable.id,
schema: dependentTable.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
); );
}), }),
}; };
@@ -101,7 +135,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
signal: abortControllerRef.current?.signal, signal: abortControllerRef.current?.signal,
}); });
} }
}, [targetDatabaseType, currentDiagram, filteredSchemas]); }, [targetDatabaseType, currentDiagram, filter]);
useEffect(() => { useEffect(() => {
if (!dialog.open) { if (!dialog.open) {

View File

@@ -7,7 +7,7 @@ import type { DatabaseEdition } from '@/lib/domain/database-edition';
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata'; import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata'; import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram'; import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack'; import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';

View File

@@ -5,7 +5,7 @@ import React, {
Suspense, Suspense,
useRef, useRef,
} from 'react'; } from 'react';
import * as monaco from 'monaco-editor'; import type * as monaco from 'monaco-editor';
import { useDialog } from '@/hooks/use-dialog'; import { useDialog } from '@/hooks/use-dialog';
import { import {
Dialog, Dialog,
@@ -23,53 +23,24 @@ import { useTranslation } from 'react-i18next';
import { Editor } from '@/components/code-snippet/code-snippet'; import { Editor } from '@/components/code-snippet/code-snippet';
import { useTheme } from '@/hooks/use-theme'; import { useTheme } from '@/hooks/use-theme';
import { AlertCircle } from 'lucide-react'; import { AlertCircle } from 'lucide-react';
import { importDBMLToDiagram, sanitizeDBML } from '@/lib/dbml-import'; import {
importDBMLToDiagram,
sanitizeDBML,
preprocessDBML,
} from '@/lib/dbml/dbml-import/dbml-import';
import { useChartDB } from '@/hooks/use-chartdb'; import { useChartDB } from '@/hooks/use-chartdb';
import { Parser } from '@dbml/core'; import { Parser } from '@dbml/core';
import { useCanvas } from '@/hooks/use-canvas'; import { useCanvas } from '@/hooks/use-canvas';
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language'; import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
import type { DBTable } from '@/lib/domain/db-table';
import { useToast } from '@/components/toast/use-toast'; import { useToast } from '@/components/toast/use-toast';
import { Spinner } from '@/components/spinner/spinner'; import { Spinner } from '@/components/spinner/spinner';
import { debounce } from '@/lib/utils'; import { debounce } from '@/lib/utils';
import { parseDBMLError } from '@/lib/dbml/dbml-import/dbml-import-error';
interface DBMLError { import {
message: string; clearErrorHighlight,
line: number; highlightErrorLine,
column: number; } from '@/components/code-snippet/dbml/utils';
}
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 { export interface ImportDBMLDialogProps extends BaseDialogProps {
withCreateEmptyDiagram?: boolean; withCreateEmptyDiagram?: boolean;
@@ -145,39 +116,8 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
} }
}, [reorder, reorderTables]); }, [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(() => { const clearDecorations = useCallback(() => {
decorationsCollection.current?.clear(); clearErrorHighlight(decorationsCollection.current);
}, []); }, []);
const validateDBML = useCallback( const validateDBML = useCallback(
@@ -189,9 +129,10 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
if (!content.trim()) return; if (!content.trim()) return;
try { try {
const sanitizedContent = sanitizeDBML(content); const preprocessedContent = preprocessDBML(content);
const sanitizedContent = sanitizeDBML(preprocessedContent);
const parser = new Parser(); const parser = new Parser();
parser.parse(sanitizedContent, 'dbml'); parser.parse(sanitizedContent, 'dbmlv2');
} catch (e) { } catch (e) {
const parsedError = parseDBMLError(e); const parsedError = parseDBMLError(e);
if (parsedError) { if (parsedError) {
@@ -199,7 +140,12 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
t('import_dbml_dialog.error.description') + t('import_dbml_dialog.error.description') +
` (1 error found - in line ${parsedError.line})` ` (1 error found - in line ${parsedError.line})`
); );
highlightErrorLine(parsedError); highlightErrorLine({
error: parsedError,
model: editorRef.current?.getModel(),
editorDecorationsCollection:
decorationsCollection.current,
});
} else { } else {
setErrorMessage( setErrorMessage(
e instanceof Error ? e.message : JSON.stringify(e) e instanceof Error ? e.message : JSON.stringify(e)
@@ -207,7 +153,7 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
} }
} }
}, },
[clearDecorations, highlightErrorLine, t] [clearDecorations, t]
); );
const debouncedValidateRef = useRef<((value: string) => void) | null>(null); const debouncedValidateRef = useRef<((value: string) => void) | null>(null);
@@ -242,13 +188,11 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
if (!dbmlContent.trim() || errorMessage) return; if (!dbmlContent.trim() || errorMessage) return;
try { try {
// Sanitize DBML content before importing const importedDiagram = await importDBMLToDiagram(dbmlContent);
const sanitizedContent = sanitizeDBML(dbmlContent);
const importedDiagram = await importDBMLToDiagram(sanitizedContent);
const tableIdsToRemove = tables const tableIdsToRemove = tables
.filter((table) => .filter((table) =>
importedDiagram.tables?.some( importedDiagram.tables?.some(
(t) => (t: DBTable) =>
t.name === table.name && t.schema === table.schema t.name === table.name && t.schema === table.schema
) )
) )
@@ -257,19 +201,21 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
const relationshipIdsToRemove = relationships const relationshipIdsToRemove = relationships
.filter((relationship) => { .filter((relationship) => {
const sourceTable = tables.find( const sourceTable = tables.find(
(table) => table.id === relationship.sourceTableId (table: DBTable) =>
table.id === relationship.sourceTableId
); );
const targetTable = tables.find( const targetTable = tables.find(
(table) => table.id === relationship.targetTableId (table: DBTable) =>
table.id === relationship.targetTableId
); );
if (!sourceTable || !targetTable) return true; if (!sourceTable || !targetTable) return true;
const replacementSourceTable = importedDiagram.tables?.find( const replacementSourceTable = importedDiagram.tables?.find(
(table) => (table: DBTable) =>
table.name === sourceTable.name && table.name === sourceTable.name &&
table.schema === sourceTable.schema table.schema === sourceTable.schema
); );
const replacementTargetTable = importedDiagram.tables?.find( const replacementTargetTable = importedDiagram.tables?.find(
(table) => (table: DBTable) =>
table.name === targetTable.name && table.name === targetTable.name &&
table.schema === targetTable.schema table.schema === targetTable.schema
); );

View File

@@ -0,0 +1,98 @@
import React, { useCallback } from 'react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/dropdown-menu/dropdown-menu';
import { Button } from '@/components/button/button';
import { Ellipsis, Layers2, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
import { useChartDB } from '@/hooks/use-chartdb';
import type { Diagram } from '@/lib/domain';
import { useStorage } from '@/hooks/use-storage';
import { cloneDiagram } from '@/lib/clone';
import { useTranslation } from 'react-i18next';
interface DiagramRowActionsMenuProps {
diagram: Diagram;
onOpen: () => void;
refetch: () => void;
numberOfDiagrams: number;
}
export const DiagramRowActionsMenu: React.FC<DiagramRowActionsMenuProps> = ({
diagram,
onOpen,
refetch,
numberOfDiagrams,
}) => {
const { diagramId } = useChartDB();
const { deleteDiagram, addDiagram } = useStorage();
const { t } = useTranslation();
const onDelete = useCallback(async () => {
deleteDiagram(diagram.id);
refetch();
if (diagram.id === diagramId || numberOfDiagrams <= 1) {
window.location.href = '/';
}
}, [deleteDiagram, diagram.id, diagramId, refetch, numberOfDiagrams]);
const onDuplicate = useCallback(async () => {
const duplicatedDiagram = cloneDiagram(diagram);
const diagramToAdd = duplicatedDiagram.diagram;
if (!diagramToAdd) {
return;
}
diagramToAdd.name = `${diagram.name} (Copy)`;
addDiagram({ diagram: diagramToAdd });
refetch();
}, [addDiagram, refetch, diagram]);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-8 p-0"
onClick={(e) => e.stopPropagation()}
>
<Ellipsis className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={onOpen}
className="flex justify-between gap-4"
>
{t('open_diagram_dialog.diagram_actions.open')}
<SquareArrowOutUpRight className="size-3.5" />
</DropdownMenuItem>
<DropdownMenuItem
onClick={onDuplicate}
className="flex justify-between gap-4"
>
{t('open_diagram_dialog.diagram_actions.duplicate')}
<Layers2 className="size-3.5" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={onDelete}
className="flex justify-between gap-4 text-red-700"
>
{t('open_diagram_dialog.diagram_actions.delete')}
<Trash2 className="size-3.5 text-red-700" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

View File

@@ -27,6 +27,7 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
import { useDebounce } from '@/hooks/use-debounce'; import { useDebounce } from '@/hooks/use-debounce';
import { DiagramRowActionsMenu } from './diagram-row-actions-menu/diagram-row-actions-menu';
export interface OpenDiagramDialogProps extends BaseDialogProps { export interface OpenDiagramDialogProps extends BaseDialogProps {
canClose?: boolean; canClose?: boolean;
@@ -46,21 +47,22 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
string | undefined string | undefined
>(); >();
useEffect(() => { const fetchDiagrams = useCallback(async () => {
setSelectedDiagramId(undefined); const diagrams = await listDiagrams({ includeTables: true });
}, [dialog.open]); setDiagrams(
diagrams.sort(
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
)
);
}, [listDiagrams]);
useEffect(() => { useEffect(() => {
const fetchDiagrams = async () => { if (!dialog.open) {
const diagrams = await listDiagrams({ includeTables: true }); return;
setDiagrams( }
diagrams.sort( setSelectedDiagramId(undefined);
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
)
);
};
fetchDiagrams(); fetchDiagrams();
}, [listDiagrams, setDiagrams, dialog.open]); }, [dialog.open, fetchDiagrams]);
const openDiagram = useCallback( const openDiagram = useCallback(
(diagramId: string) => { (diagramId: string) => {
@@ -166,6 +168,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
'open_diagram_dialog.table_columns.tables_count' 'open_diagram_dialog.table_columns.tables_count'
)} )}
</TableHead> </TableHead>
<TableHead />
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@@ -221,6 +224,19 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
<TableCell className="text-center"> <TableCell className="text-center">
{diagram.tables?.length} {diagram.tables?.length}
</TableCell> </TableCell>
<TableCell className="items-center p-0 pr-1 text-right">
<DiagramRowActionsMenu
diagram={diagram}
onOpen={() => {
openDiagram(diagram.id);
closeOpenDiagramDialog();
}}
numberOfDiagrams={
diagrams.length
}
refetch={fetchDiagrams}
/>
</TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDialog } from '@/hooks/use-dialog'; import { useDialog } from '@/hooks/use-dialog';
import { import {
Dialog, Dialog,
@@ -17,11 +17,23 @@ import type { DBSchema } from '@/lib/domain/db-schema';
import { schemaNameToSchemaId } from '@/lib/domain/db-schema'; import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
import type { BaseDialogProps } from '../common/base-dialog-props'; import type { BaseDialogProps } from '../common/base-dialog-props';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Input } from '@/components/input/input';
import { Separator } from '@/components/separator/separator';
import { Group, SquarePlus } from 'lucide-react';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/tooltip/tooltip';
import { useChartDB } from '@/hooks/use-chartdb';
import { defaultSchemas } from '@/lib/data/default-schemas';
import { Label } from '@/components/label/label';
export interface TableSchemaDialogProps extends BaseDialogProps { export interface TableSchemaDialogProps extends BaseDialogProps {
table?: DBTable; table?: DBTable;
schemas: DBSchema[]; schemas: DBSchema[];
onConfirm: (schema: string) => void; onConfirm: ({ schema }: { schema: DBSchema }) => void;
allowSchemaCreation?: boolean;
} }
export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
@@ -29,27 +41,73 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
table, table,
schemas, schemas,
onConfirm, onConfirm,
allowSchemaCreation = false,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedSchema, setSelectedSchema] = React.useState<string>( const { databaseType } = useChartDB();
const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
table?.schema table?.schema
? schemaNameToSchemaId(table.schema) ? schemaNameToSchemaId(table.schema)
: (schemas?.[0]?.id ?? '') : (schemas?.[0]?.id ?? '')
); );
const allowSchemaSelection = useMemo(
() => schemas && schemas.length > 0,
[schemas]
);
const defaultSchemaName = useMemo(
() => defaultSchemas?.[databaseType],
[databaseType]
);
const [isCreatingNew, setIsCreatingNew] =
useState<boolean>(!allowSchemaSelection);
const [newSchemaName, setNewSchemaName] = useState<string>(
allowSchemaCreation && !allowSchemaSelection
? (defaultSchemaName ?? '')
: ''
);
useEffect(() => { useEffect(() => {
if (!dialog.open) return; if (!dialog.open) return;
setSelectedSchema( setSelectedSchemaId(
table?.schema table?.schema
? schemaNameToSchemaId(table.schema) ? schemaNameToSchemaId(table.schema)
: (schemas?.[0]?.id ?? '') : (schemas?.[0]?.id ?? '')
); );
}, [dialog.open, schemas, table?.schema]); setIsCreatingNew(!allowSchemaSelection);
setNewSchemaName(
allowSchemaCreation && !allowSchemaSelection
? (defaultSchemaName ?? '')
: ''
);
}, [
defaultSchemaName,
dialog.open,
schemas,
table?.schema,
allowSchemaSelection,
allowSchemaCreation,
]);
const { closeTableSchemaDialog } = useDialog(); const { closeTableSchemaDialog } = useDialog();
const handleConfirm = useCallback(() => { const handleConfirm = useCallback(() => {
onConfirm(selectedSchema); if (isCreatingNew && newSchemaName.trim()) {
}, [onConfirm, selectedSchema]); const newSchema: DBSchema = {
id: schemaNameToSchemaId(newSchemaName.trim()),
name: newSchemaName.trim(),
tableCount: 0,
};
onConfirm({ schema: newSchema });
} else {
const schema = schemas.find((s) => s.id === selectedSchemaId);
if (!schema) return;
onConfirm({ schema });
}
}, [onConfirm, selectedSchemaId, schemas, isCreatingNew, newSchemaName]);
const schemaOptions: SelectBoxOption[] = useMemo( const schemaOptions: SelectBoxOption[] = useMemo(
() => () =>
@@ -60,6 +118,25 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
[schemas] [schemas]
); );
const renderSwitchCreateOrSelectButton = useCallback(
() => (
<Button
variant="outline"
className="w-full justify-start"
onClick={() => setIsCreatingNew(!isCreatingNew)}
disabled={!allowSchemaSelection || !allowSchemaCreation}
>
{!isCreatingNew ? (
<SquarePlus className="mr-2 size-4 " />
) : (
<Group className="mr-2 size-4 " />
)}
{isCreatingNew ? 'Select existing schema' : 'Create new schema'}
</Button>
),
[isCreatingNew, allowSchemaSelection, allowSchemaCreation]
);
return ( return (
<Dialog <Dialog
{...dialog} {...dialog}
@@ -67,48 +144,106 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
if (!open) { if (!open) {
closeTableSchemaDialog(); closeTableSchemaDialog();
} }
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
}} }}
> >
<DialogContent className="flex flex-col" showClose> <DialogContent className="flex flex-col" showClose>
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{table {!allowSchemaSelection && allowSchemaCreation
? t('update_table_schema_dialog.title') ? t('create_table_schema_dialog.title')
: t('new_table_schema_dialog.title')} : table
? t('update_table_schema_dialog.title')
: t('new_table_schema_dialog.title')}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
{table {!allowSchemaSelection && allowSchemaCreation
? t('update_table_schema_dialog.description', { ? t('create_table_schema_dialog.description')
tableName: table.name, : table
}) ? t('update_table_schema_dialog.description', {
: t('new_table_schema_dialog.description')} tableName: table.name,
})
: t('new_table_schema_dialog.description')}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-1"> <div className="grid gap-4 py-1">
<div className="grid w-full items-center gap-4"> <div className="grid w-full items-center gap-4">
<SelectBox {!isCreatingNew ? (
options={schemaOptions} <SelectBox
multiple={false} options={schemaOptions}
value={selectedSchema} multiple={false}
onChange={(value) => value={selectedSchemaId}
setSelectedSchema(value as string) onChange={(value) =>
} setSelectedSchemaId(value as string)
/> }
/>
) : (
<div className="flex flex-col gap-2">
{allowSchemaCreation &&
!allowSchemaSelection ? (
<Label htmlFor="new-schema-name">
Schema Name
</Label>
) : null}
<Input
id="new-schema-name"
value={newSchemaName}
onChange={(e) =>
setNewSchemaName(e.target.value)
}
placeholder={`Enter schema name.${defaultSchemaName ? ` e.g. ${defaultSchemaName}.` : ''}`}
autoFocus
/>
</div>
)}
{allowSchemaCreation && allowSchemaSelection ? (
<>
<div className="relative">
<Separator className="my-2" />
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-2 text-xs text-muted-foreground">
or
</span>
</div>
{allowSchemaSelection ? (
renderSwitchCreateOrSelectButton()
) : (
<Tooltip>
<TooltipTrigger asChild>
<span>
{renderSwitchCreateOrSelectButton()}
</span>
</TooltipTrigger>
<TooltipContent>
<p>No existing schemas available</p>
</TooltipContent>
</Tooltip>
)}
</>
) : null}
</div> </div>
</div> </div>
<DialogFooter className="flex gap-1 md:justify-between"> <DialogFooter className="flex gap-1 md:justify-between">
<DialogClose asChild> <DialogClose asChild>
<Button variant="secondary"> <Button variant="secondary">
{table {isCreatingNew
? t('update_table_schema_dialog.cancel') ? t('create_table_schema_dialog.cancel')
: t('new_table_schema_dialog.cancel')} : table
? t('update_table_schema_dialog.cancel')
: t('new_table_schema_dialog.cancel')}
</Button> </Button>
</DialogClose> </DialogClose>
<DialogClose asChild> <DialogClose asChild>
<Button onClick={handleConfirm}> <Button
{table onClick={handleConfirm}
? t('update_table_schema_dialog.confirm') disabled={isCreatingNew && !newSchemaName.trim()}
: t('new_table_schema_dialog.confirm')} >
{isCreatingNew
? t('create_table_schema_dialog.create')
: table
? t('update_table_schema_dialog.confirm')
: t('new_table_schema_dialog.confirm')}
</Button> </Button>
</DialogClose> </DialogClose>
</DialogFooter> </DialogFooter>

View File

@@ -83,6 +83,7 @@
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
overscroll-behavior-x: none;
} }
.text-editable { .text-editable {
@@ -154,3 +155,29 @@
background-size: 650%; background-size: 650%;
} }
} }
/* Edit button emphasis animation */
@keyframes dbml_edit-button-emphasis {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
background-color: rgba(59, 130, 246, 0);
}
50% {
transform: scale(1.1);
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
background-color: rgba(59, 130, 246, 0.1);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
background-color: rgba(59, 130, 246, 0);
}
}
.dbml-edit-button-emphasis {
animation: dbml_edit-button-emphasis 0.6s ease-in-out;
animation-iteration-count: 1;
position: relative;
z-index: 10;
}

View File

@@ -0,0 +1,50 @@
import { useEffect, useCallback, type RefObject } from 'react';
/**
* Custom hook that handles click outside detection with capture phase
* to work properly with React Flow canvas and other event-stopping elements
*/
export function useClickOutside(
ref: RefObject<HTMLElement>,
handler: () => void,
isActive = true
) {
useEffect(() => {
if (!isActive) return;
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
handler();
}
};
// Use capture phase to catch events before React Flow or other libraries can stop them
document.addEventListener('mousedown', handleClickOutside, true);
return () => {
document.removeEventListener('mousedown', handleClickOutside, true);
};
}, [ref, handler, isActive]);
}
/**
* Specialized version of useClickOutside for edit mode inputs
* Adds a small delay to prevent race conditions with blur events
*/
export function useEditClickOutside(
inputRef: RefObject<HTMLElement>,
editMode: boolean,
onSave: () => void,
delay = 100
) {
const handleClickOutside = useCallback(() => {
if (editMode) {
// Small delay to ensure any pending state updates are processed
setTimeout(() => {
onSave();
}, delay);
}
}, [editMode, onSave, delay]);
useClickOutside(inputRef, handleClickOutside, editMode);
}

142
src/hooks/use-focus-on.ts Normal file
View File

@@ -0,0 +1,142 @@
import { useCallback } from 'react';
import { useReactFlow } from '@xyflow/react';
import { useLayout } from '@/hooks/use-layout';
import { useBreakpoint } from '@/hooks/use-breakpoint';
interface FocusOptions {
select?: boolean;
}
export const useFocusOn = () => {
const { fitView, setNodes, setEdges } = useReactFlow();
const { hideSidePanel } = useLayout();
const { isMd: isDesktop } = useBreakpoint('md');
const focusOnArea = useCallback(
(areaId: string, options: FocusOptions = {}) => {
const { select = true } = options;
if (select) {
setNodes((nodes) =>
nodes.map((node) =>
node.id === areaId
? {
...node,
selected: true,
}
: {
...node,
selected: false,
}
)
);
}
fitView({
duration: 500,
maxZoom: 1,
minZoom: 1,
nodes: [
{
id: areaId,
},
],
});
if (!isDesktop) {
hideSidePanel();
}
},
[fitView, setNodes, hideSidePanel, isDesktop]
);
const focusOnTable = useCallback(
(tableId: string, options: FocusOptions = {}) => {
const { select = true } = options;
if (select) {
setNodes((nodes) =>
nodes.map((node) =>
node.id === tableId
? {
...node,
selected: true,
}
: {
...node,
selected: false,
}
)
);
}
fitView({
duration: 500,
maxZoom: 1,
minZoom: 1,
nodes: [
{
id: tableId,
},
],
});
if (!isDesktop) {
hideSidePanel();
}
},
[fitView, setNodes, hideSidePanel, isDesktop]
);
const focusOnRelationship = useCallback(
(
relationshipId: string,
sourceTableId: string,
targetTableId: string,
options: FocusOptions = {}
) => {
const { select = true } = options;
if (select) {
setEdges((edges) =>
edges.map((edge) =>
edge.id === relationshipId
? {
...edge,
selected: true,
}
: {
...edge,
selected: false,
}
)
);
}
fitView({
duration: 500,
maxZoom: 1,
minZoom: 1,
nodes: [
{
id: sourceTableId,
},
{
id: targetTableId,
},
],
});
if (!isDesktop) {
hideSidePanel();
}
},
[fitView, setEdges, hideSidePanel, isDesktop]
);
return {
focusOnArea,
focusOnTable,
focusOnRelationship,
};
};

View File

@@ -0,0 +1,320 @@
import { useCallback, useMemo, useState, useEffect } from 'react';
import { useChartDB } from './use-chartdb';
import { useDebounce } from './use-debounce-v2';
import type { DBField, DBTable } from '@/lib/domain';
import type {
SelectBoxOption,
SelectBoxProps,
} from '@/components/select-box/select-box';
import {
dataTypeDataToDataType,
sortedDataTypeMap,
} from '@/lib/data/data-types/data-types';
import { generateDBFieldSuffix } from '@/lib/domain/db-field';
import type { DataTypeData } from '@/lib/data/data-types/data-types';
const generateFieldRegexPatterns = (
dataType: DataTypeData
): {
regex?: string;
extractRegex?: RegExp;
} => {
if (!dataType.fieldAttributes) {
return { regex: undefined, extractRegex: undefined };
}
const typeName = dataType.name;
const fieldAttributes = dataType.fieldAttributes;
if (fieldAttributes.hasCharMaxLength) {
if (fieldAttributes.hasCharMaxLengthOption) {
return {
regex: `^${typeName}\\((\\d+|[mM][aA][xX])\\)$`,
extractRegex: /\((\d+|max)\)/i,
};
}
return {
regex: `^${typeName}\\(\\d+\\)$`,
extractRegex: /\((\d+)\)/,
};
}
if (fieldAttributes.precision && fieldAttributes.scale) {
return {
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*(?:,\\s*\\d+\\s*)?\\)$`,
extractRegex: new RegExp(
`${typeName}\\s*\\(\\s*(\\d+)\\s*(?:,\\s*(\\d+)\\s*)?\\)`
),
};
}
if (fieldAttributes.precision) {
return {
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*\\)$`,
extractRegex: /\((\d+)\)/,
};
}
return { regex: undefined, extractRegex: undefined };
};
export const useUpdateTableField = (
table: DBTable,
field: DBField,
customUpdateField?: (attrs: Partial<DBField>) => void
) => {
const {
databaseType,
customTypes,
updateField: chartDBUpdateField,
removeField: chartDBRemoveField,
} = useChartDB();
// Local state for responsive UI
const [localFieldName, setLocalFieldName] = useState(field.name);
const [localNullable, setLocalNullable] = useState(field.nullable);
const [localPrimaryKey, setLocalPrimaryKey] = useState(field.primaryKey);
// Update local state when field properties change externally
useEffect(() => {
setLocalFieldName(field.name);
setLocalNullable(field.nullable);
setLocalPrimaryKey(field.primaryKey);
}, [field.name, field.nullable, field.primaryKey]);
// Use custom updateField if provided, otherwise use the chartDB one
const updateField = useMemo(
() =>
customUpdateField
? (
_tableId: string,
_fieldId: string,
attrs: Partial<DBField>
) => customUpdateField(attrs)
: chartDBUpdateField,
[customUpdateField, chartDBUpdateField]
);
// Calculate primary key fields for validation
const primaryKeyFields = useMemo(() => {
return table.fields.filter((f) => f.primaryKey);
}, [table.fields]);
const primaryKeyCount = useMemo(
() => primaryKeyFields.length,
[primaryKeyFields.length]
);
// Generate data type options for select box
const dataFieldOptions = useMemo(() => {
const standardTypes: SelectBoxOption[] = sortedDataTypeMap[
databaseType
].map((type) => {
const regexPatterns = generateFieldRegexPatterns(type);
return {
label: type.name,
value: type.id,
regex: regexPatterns.regex,
extractRegex: regexPatterns.extractRegex,
group: customTypes?.length ? 'Standard Types' : undefined,
};
});
if (!customTypes?.length) {
return standardTypes;
}
// Add custom types as options
const customTypeOptions: SelectBoxOption[] = customTypes.map(
(type) => ({
label: type.name,
value: type.name,
description:
type.kind === 'enum' ? `${type.values?.join(' | ')}` : '',
group: 'Custom Types',
})
);
return [...standardTypes, ...customTypeOptions];
}, [databaseType, customTypes]);
// Handle data type change
const handleDataTypeChange = useCallback<
NonNullable<SelectBoxProps['onChange']>
>(
(value, regexMatches) => {
const dataType = sortedDataTypeMap[databaseType].find(
(v) => v.id === value
) ?? {
id: value as string,
name: value as string,
};
let characterMaximumLength: string | undefined = undefined;
let precision: number | undefined = undefined;
let scale: number | undefined = undefined;
if (regexMatches?.length) {
if (dataType?.fieldAttributes?.hasCharMaxLength) {
characterMaximumLength = regexMatches[1]?.toLowerCase();
} else if (
dataType?.fieldAttributes?.precision &&
dataType?.fieldAttributes?.scale
) {
precision = parseInt(regexMatches[1]);
scale = regexMatches[2]
? parseInt(regexMatches[2])
: undefined;
} else if (dataType?.fieldAttributes?.precision) {
precision = parseInt(regexMatches[1]);
}
} else {
if (
dataType?.fieldAttributes?.hasCharMaxLength &&
field.characterMaximumLength
) {
characterMaximumLength = field.characterMaximumLength;
}
if (dataType?.fieldAttributes?.precision && field.precision) {
precision = field.precision;
}
if (dataType?.fieldAttributes?.scale && field.scale) {
scale = field.scale;
}
}
updateField(table.id, field.id, {
characterMaximumLength,
precision,
scale,
increment: undefined,
default: undefined,
type: dataTypeDataToDataType(
dataType ?? {
id: value as string,
name: value as string,
}
),
});
},
[
updateField,
databaseType,
field.characterMaximumLength,
field.precision,
field.scale,
field.id,
table.id,
]
);
// Debounced update for field name
const debouncedNameUpdate = useDebounce(
useCallback(
(value: string) => {
if (value.trim() !== field.name) {
updateField(table.id, field.id, { name: value });
}
},
[updateField, table.id, field.id, field.name]
),
300 // 300ms debounce for text input
);
// Debounced update for nullable toggle
const debouncedNullableUpdate = useDebounce(
useCallback(
(value: boolean) => {
updateField(table.id, field.id, { nullable: value });
},
[updateField, table.id, field.id]
),
100 // 100ms debounce for toggle
);
// Debounced update for primary key toggle
const debouncedPrimaryKeyUpdate = useDebounce(
useCallback(
(value: boolean, primaryKeyCount: number) => {
if (value) {
// When setting as primary key
const updates: Partial<DBField> = {
primaryKey: true,
};
// Only auto-set unique if this will be the only primary key
if (primaryKeyCount === 0) {
updates.unique = true;
}
updateField(table.id, field.id, updates);
} else {
// When removing primary key
updateField(table.id, field.id, {
primaryKey: false,
});
}
},
[updateField, table.id, field.id]
),
100 // 100ms debounce for toggle
);
// Handle primary key toggle with optimistic update
const handlePrimaryKeyToggle = useCallback(
(value: boolean) => {
setLocalPrimaryKey(value);
debouncedPrimaryKeyUpdate(value, primaryKeyCount);
},
[primaryKeyCount, debouncedPrimaryKeyUpdate]
);
// Handle nullable toggle with optimistic update
const handleNullableToggle = useCallback(
(value: boolean) => {
setLocalNullable(value);
debouncedNullableUpdate(value);
},
[debouncedNullableUpdate]
);
// Handle name change with optimistic update
const handleNameChange = useCallback(
(value: string) => {
setLocalFieldName(value);
debouncedNameUpdate(value);
},
[debouncedNameUpdate]
);
// Utility function to generate field suffix for display
const generateFieldSuffix = useCallback(
(typeId?: string) => {
return generateDBFieldSuffix(field, {
databaseType,
forceExtended: true,
typeId,
});
},
[field, databaseType]
);
const removeField = useCallback(() => {
chartDBRemoveField(table.id, field.id);
}, [chartDBRemoveField, table.id, field.id]);
return {
dataFieldOptions,
handleDataTypeChange,
handlePrimaryKeyToggle,
handleNullableToggle,
handleNameChange,
generateFieldSuffix,
primaryKeyCount,
fieldName: localFieldName,
nullable: localNullable,
primaryKey: localPrimaryKey,
removeField,
};
};

View File

@@ -0,0 +1,42 @@
import { useCallback, useState, useEffect } from 'react';
import { useChartDB } from './use-chartdb';
import { useDebounce } from './use-debounce-v2';
import type { DBTable } from '@/lib/domain';
// Hook for updating table properties with debouncing for performance
export const useUpdateTable = (table: DBTable) => {
const { updateTable: chartDBUpdateTable } = useChartDB();
const [localTableName, setLocalTableName] = useState(table.name);
// Debounced update function
const debouncedUpdate = useDebounce(
useCallback(
(value: string) => {
if (value.trim() && value.trim() !== table.name) {
chartDBUpdateTable(table.id, { name: value.trim() });
}
},
[chartDBUpdateTable, table.id, table.name]
),
1000 // 1000ms debounce
);
// Update local state immediately for responsive UI
const handleTableNameChange = useCallback(
(value: string) => {
setLocalTableName(value);
debouncedUpdate(value);
},
[debouncedUpdate]
);
// Update local state when table name changes externally
useEffect(() => {
setLocalTableName(table.name);
}, [table.name]);
return {
tableName: localTableName,
handleTableNameChange,
};
};

View File

@@ -23,23 +23,25 @@ import { bn, bnMetadata } from './locales/bn';
import { gu, guMetadata } from './locales/gu'; import { gu, guMetadata } from './locales/gu';
import { vi, viMetadata } from './locales/vi'; import { vi, viMetadata } from './locales/vi';
import { ar, arMetadata } from './locales/ar'; import { ar, arMetadata } from './locales/ar';
import { hr, hrMetadata } from './locales/hr';
export const languages: LanguageMetadata[] = [ export const languages: LanguageMetadata[] = [
enMetadata, enMetadata,
esMetadata,
frMetadata, frMetadata,
deMetadata, deMetadata,
esMetadata,
ukMetadata,
ruMetadata,
trMetadata,
hrMetadata,
pt_BRMetadata,
hiMetadata, hiMetadata,
jaMetadata, jaMetadata,
ko_KRMetadata, ko_KRMetadata,
pt_BRMetadata,
ukMetadata,
ruMetadata,
zh_CNMetadata, zh_CNMetadata,
zh_TWMetadata, zh_TWMetadata,
neMetadata, neMetadata,
mrMetadata, mrMetadata,
trMetadata,
id_IDMetadata, id_IDMetadata,
teMetadata, teMetadata,
bnMetadata, bnMetadata,
@@ -70,6 +72,7 @@ const resources = {
gu, gu,
vi, vi,
ar, ar,
hr,
}; };
i18n.use(LanguageDetector) i18n.use(LanguageDetector)

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ar: LanguageTranslation = { export const ar: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'جديد',
browse: 'تصفح',
tables: 'الجداول',
refs: 'المراجع',
areas: 'المناطق',
dependencies: 'التبعيات',
custom_types: 'الأنواع المخصصة',
},
menu: { menu: {
file: { actions: {
file: 'ملف', actions: 'الإجراءات',
new: 'جديد', new: 'جديد...',
open: 'فتح', browse: 'تصفح...',
save: 'حفظ', save: 'حفظ',
import: 'استيراد قاعدة بيانات', import: 'استيراد قاعدة بيانات',
export_sql: 'SQL تصدير', export_sql: 'SQL تصدير',
export_as: 'تصدير كـ', export_as: 'تصدير كـ',
delete_diagram: 'حذف الرسم البياني', delete_diagram: 'حذف',
exit: 'خروج',
}, },
edit: { edit: {
edit: 'تحرير', edit: 'تحرير',
@@ -26,7 +34,10 @@ export const ar: LanguageTranslation = {
hide_sidebar: 'إخفاء الشريط الجانبي', hide_sidebar: 'إخفاء الشريط الجانبي',
hide_cardinality: 'إخفاء الكاردينالية', hide_cardinality: 'إخفاء الكاردينالية',
show_cardinality: 'إظهار الكاردينالية', show_cardinality: 'إظهار الكاردينالية',
hide_field_attributes: 'إخفاء خصائص الحقل',
show_field_attributes: 'إظهار خصائص الحقل',
zoom_on_scroll: 'تكبير/تصغير عند التمرير', zoom_on_scroll: 'تكبير/تصغير عند التمرير',
show_views: 'عروض قاعدة البيانات',
theme: 'المظهر', theme: 'المظهر',
show_dependencies: 'إظهار الاعتمادات', show_dependencies: 'إظهار الاعتمادات',
hide_dependencies: 'إخفاء الاعتمادات', hide_dependencies: 'إخفاء الاعتمادات',
@@ -63,22 +74,13 @@ export const ar: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'إعادة ترتيب الرسم البياني', title: 'ترتيب تلقائي للرسم البياني',
description: description:
'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟', 'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
reorder: 'إعادة ترتيب', reorder: 'ترتيب تلقائي',
cancel: 'إلغاء', cancel: 'إلغاء',
}, },
multiple_schemas_alert: {
title: 'مخططات متعددة',
description:
'{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
dont_show_again: 'لا تظهره مجدداً',
change_schema: 'تغيير',
none: 'لا شيء',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'فشل النسخ', title: 'فشل النسخ',
@@ -113,14 +115,11 @@ export const ar: LanguageTranslation = {
copied: '!تم النسخ', copied: '!تم النسخ',
side_panel: { side_panel: {
schema: ':المخطط',
filter_by_schema: 'تصفية حسب المخطط',
search_schema: '...بحث في المخطط',
no_schemas_found: '.لم يتم العثور على مخططات',
view_all_options: '...عرض جميع الخيارات', view_all_options: '...عرض جميع الخيارات',
tables_section: { tables_section: {
tables: 'الجداول', tables: 'الجداول',
add_table: 'إضافة جدول', add_table: 'إضافة جدول',
add_view: 'إضافة عرض',
filter: 'تصفية', filter: 'تصفية',
collapse: 'طي الكل', collapse: 'طي الكل',
// TODO: Translate // TODO: Translate
@@ -146,16 +145,22 @@ export const ar: LanguageTranslation = {
field_actions: { field_actions: {
title: 'خصائص الحقل', title: 'خصائص الحقل',
unique: 'فريد', unique: 'فريد',
auto_increment: 'زيادة تلقائية',
comments: 'تعليقات', comments: 'تعليقات',
no_comments: 'لا يوجد تعليقات', no_comments: 'لا يوجد تعليقات',
delete_field: 'حذف الحقل', delete_field: 'حذف الحقل',
// TODO: Translate // TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'الدقة',
scale: 'النطاق',
default_value: 'Default Value',
no_default: 'No default',
}, },
index_actions: { index_actions: {
title: 'خصائص الفهرس', title: 'خصائص الفهرس',
name: 'الإسم', name: 'الإسم',
unique: 'فريد', unique: 'فريد',
index_type: 'نوع الفهرس',
delete_index: 'حذف الفهرس', delete_index: 'حذف الفهرس',
}, },
table_actions: { table_actions: {
@@ -172,12 +177,15 @@ export const ar: LanguageTranslation = {
description: 'أنشئ جدولاً للبدء', description: 'أنشئ جدولاً للبدء',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'العلاقات', refs: 'المراجع',
filter: 'تصفية', filter: 'تصفية',
add_relationship: 'إضافة علاقة',
collapse: 'طي الكل', collapse: 'طي الكل',
add_relationship: 'إضافة علاقة',
relationships: 'العلاقات',
dependencies: 'الاعتمادات',
relationship: { relationship: {
relationship: 'العلاقة',
primary: 'الجدول الأساسي', primary: 'الجدول الأساسي',
foreign: 'الجدول المرتبط', foreign: 'الجدول المرتبط',
cardinality: 'الكاردينالية', cardinality: 'الكاردينالية',
@@ -187,16 +195,8 @@ export const ar: LanguageTranslation = {
delete_relationship: 'حذف', delete_relationship: 'حذف',
}, },
}, },
empty_state: {
title: 'لا توجد علاقات',
description: 'إنشئ علاقة لربط الجداول',
},
},
dependencies_section: {
dependencies: 'الاعتمادات',
filter: 'تصفية',
collapse: 'طي الكل',
dependency: { dependency: {
dependency: 'الاعتماد',
table: 'الجدول', table: 'الجدول',
dependent_table: 'عرض الاعتمادات', dependent_table: 'عرض الاعتمادات',
delete_dependency: 'حذف', delete_dependency: 'حذف',
@@ -206,8 +206,8 @@ export const ar: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'لا توجد اعتمادات', title: 'لا توجد علاقات',
description: 'إنشاء اعتماد للبدء', description: 'إنشاء علاقة للبدء',
}, },
}, },
@@ -248,12 +248,16 @@ export const ar: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'لم يتم تحديد قيم التعداد',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -267,8 +271,13 @@ export const ar: LanguageTranslation = {
show_all: 'عرض الكل', show_all: 'عرض الكل',
undo: 'تراجع', undo: 'تراجع',
redo: 'إعادة', redo: 'إعادة',
reorder_diagram: 'إعادة ترتيب الرسم البياني', reorder_diagram: 'ترتيب تلقائي للرسم البياني',
highlight_overlapping_tables: 'تمييز الجداول المتداخلة', highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
// TODO: Translate
filter: 'Filter Tables',
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -305,7 +314,7 @@ export const ar: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'فتح مخطط', title: 'فتح قاعدة بيانات',
description: 'اختر مخططًا لفتحه من القائمة ادناه', description: 'اختر مخططًا لفتحه من القائمة ادناه',
table_columns: { table_columns: {
name: 'الإسم', name: 'الإسم',
@@ -315,6 +324,12 @@ export const ar: LanguageTranslation = {
}, },
cancel: 'إلغاء', cancel: 'إلغاء',
open: 'فتح', open: 'فتح',
diagram_actions: {
open: 'فتح',
duplicate: 'تكرار',
delete: 'حذف',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -400,6 +415,13 @@ export const ar: LanguageTranslation = {
cancel: 'إلغاء', cancel: 'إلغاء',
confirm: 'تغيير', confirm: 'تغيير',
}, },
create_table_schema_dialog: {
title: 'إنشاء مخطط جديد',
description:
'لا توجد مخططات حتى الآن. قم بإنشاء أول مخطط لتنظيم جداولك.',
create: 'إنشاء',
cancel: 'إلغاء',
},
star_us_dialog: { star_us_dialog: {
title: '!ساعدنا على التحسن', title: '!ساعدنا على التحسن',
@@ -453,6 +475,7 @@ export const ar: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'جدول جديد', new_table: 'جدول جديد',
new_view: 'عرض جديد',
new_relationship: 'علاقة جديدة', new_relationship: 'علاقة جديدة',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -474,6 +497,8 @@ export const ar: LanguageTranslation = {
language_select: { language_select: {
change_language: 'اللغة', change_language: 'اللغة',
}, },
on: 'تشغيل',
off: 'إيقاف',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const bn: LanguageTranslation = { export const bn: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'নতুন',
browse: 'ব্রাউজ',
tables: 'টেবিল',
refs: 'রেফস',
areas: 'এলাকা',
dependencies: 'নির্ভরতা',
custom_types: 'কাস্টম টাইপ',
},
menu: { menu: {
file: { actions: {
file: 'ফাইল', actions: 'কার্য',
new: 'নতুন', new: 'নতুন...',
open: 'খুলুন', browse: 'ব্রাউজ করুন...',
save: 'সংরক্ষণ করুন', save: 'সংরক্ষণ করুন',
import: 'ডাটাবেস আমদানি করুন', import: 'ডাটাবেস আমদানি করুন',
export_sql: 'SQL রপ্তানি করুন', export_sql: 'SQL রপ্তানি করুন',
export_as: 'রূপে রপ্তানি করুন', export_as: 'রূপে রপ্তানি করুন',
delete_diagram: 'ডায়াগ্রাম মুছুন', delete_diagram: 'মুছুন',
exit: 'প্রস্থান করুন',
}, },
edit: { edit: {
edit: 'সম্পাদনা', edit: 'সম্পাদনা',
@@ -26,7 +34,10 @@ export const bn: LanguageTranslation = {
hide_sidebar: 'সাইডবার লুকান', hide_sidebar: 'সাইডবার লুকান',
hide_cardinality: 'কার্ডিনালিটি লুকান', hide_cardinality: 'কার্ডিনালিটি লুকান',
show_cardinality: 'কার্ডিনালিটি দেখান', show_cardinality: 'কার্ডিনালিটি দেখান',
hide_field_attributes: 'ফিল্ড অ্যাট্রিবিউট লুকান',
show_field_attributes: 'ফিল্ড অ্যাট্রিবিউট দেখান',
zoom_on_scroll: 'স্ক্রলে জুম করুন', zoom_on_scroll: 'স্ক্রলে জুম করুন',
show_views: 'ডাটাবেস ভিউ',
theme: 'থিম', theme: 'থিম',
show_dependencies: 'নির্ভরতাগুলি দেখান', show_dependencies: 'নির্ভরতাগুলি দেখান',
hide_dependencies: 'নির্ভরতাগুলি লুকান', hide_dependencies: 'নির্ভরতাগুলি লুকান',
@@ -64,22 +75,13 @@ export const bn: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'ডায়াগ্রাম পুনর্বিন্যাস করুন', title: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
description: description:
'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?', 'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?',
reorder: 'পুনর্বিন্যাস করুন', reorder: 'স্বয়ংক্রিয় সাজান',
cancel: 'বাতিল করুন', cancel: 'বাতিল করুন',
}, },
multiple_schemas_alert: {
title: 'বহু স্কিমা',
description:
'{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
dont_show_again: 'পুনরায় দেখাবেন না',
change_schema: 'পরিবর্তন করুন',
none: 'কিছুই না',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'কপি ব্যর্থ হয়েছে', title: 'কপি ব্যর্থ হয়েছে',
@@ -114,14 +116,11 @@ export const bn: LanguageTranslation = {
copied: 'অনুলিপি সম্পন্ন!', copied: 'অনুলিপি সম্পন্ন!',
side_panel: { side_panel: {
schema: 'স্কিমা:',
filter_by_schema: 'স্কিমা দ্বারা ফিল্টার করুন',
search_schema: 'স্কিমা খুঁজুন...',
no_schemas_found: 'কোনো স্কিমা পাওয়া যায়নি।',
view_all_options: 'সমস্ত বিকল্প দেখুন...', view_all_options: 'সমস্ত বিকল্প দেখুন...',
tables_section: { tables_section: {
tables: 'টেবিল', tables: 'টেবিল',
add_table: 'টেবিল যোগ করুন', add_table: 'টেবিল যোগ করুন',
add_view: 'ভিউ যোগ করুন',
filter: 'ফিল্টার', filter: 'ফিল্টার',
collapse: 'সব ভাঁজ করুন', collapse: 'সব ভাঁজ করুন',
// TODO: Translate // TODO: Translate
@@ -147,16 +146,23 @@ export const bn: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ফিল্ড কর্ম', title: 'ফিল্ড কর্ম',
unique: 'অদ্বিতীয়', unique: 'অদ্বিতীয়',
auto_increment: 'স্বয়ংক্রিয় বৃদ্ধি',
comments: 'মন্তব্য', comments: 'মন্তব্য',
no_comments: 'কোনো মন্তব্য নেই', no_comments: 'কোনো মন্তব্য নেই',
delete_field: 'ফিল্ড মুছুন', delete_field: 'ফিল্ড মুছুন',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'নির্ভুলতা',
scale: 'স্কেল',
}, },
index_actions: { index_actions: {
title: 'ইনডেক্স কর্ম', title: 'ইনডেক্স কর্ম',
name: 'নাম', name: 'নাম',
unique: 'অদ্বিতীয়', unique: 'অদ্বিতীয়',
index_type: 'ইনডেক্স ধরন',
delete_index: 'ইনডেক্স মুছুন', delete_index: 'ইনডেক্স মুছুন',
}, },
table_actions: { table_actions: {
@@ -173,14 +179,17 @@ export const bn: LanguageTranslation = {
description: 'শুরু করতে একটি টেবিল তৈরি করুন', description: 'শুরু করতে একটি টেবিল তৈরি করুন',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'সম্পর্ক', refs: 'রেফস',
filter: 'ফিল্টার', filter: 'ফিল্টার',
add_relationship: 'সম্পর্ক যোগ করুন',
collapse: 'সব ভাঁজ করুন', collapse: 'সব ভাঁজ করুন',
add_relationship: 'সম্পর্ক যোগ করুন',
relationships: 'সম্পর্ক',
dependencies: 'নির্ভরতাগুলি',
relationship: { relationship: {
relationship: 'সম্পর্ক',
primary: 'প্রাথমিক টেবিল', primary: 'প্রাথমিক টেবিল',
foreign: 'বিদেশি টেবিল', foreign: 'রেফারেন্স করা টেবিল',
cardinality: 'কার্ডিনালিটি', cardinality: 'কার্ডিনালিটি',
delete_relationship: 'মুছুন', delete_relationship: 'মুছুন',
relationship_actions: { relationship_actions: {
@@ -188,27 +197,19 @@ export const bn: LanguageTranslation = {
delete_relationship: 'মুছুন', delete_relationship: 'মুছুন',
}, },
}, },
empty_state: {
title: 'কোনো সম্পর্ক নেই',
description: 'টেবিল সংযোগ করতে একটি সম্পর্ক তৈরি করুন',
},
},
dependencies_section: {
dependencies: 'নির্ভরতাগুলি',
filter: 'ফিল্টার',
collapse: 'ভাঁজ করুন',
dependency: { dependency: {
dependency: 'নির্ভরতা',
table: 'টেবিল', table: 'টেবিল',
dependent_table: 'নির্ভরশীল টেবিল', dependent_table: 'নির্ভরশীল ভিউ',
delete_dependency: 'নির্ভরতা মুছুন', delete_dependency: 'মুছুন',
dependency_actions: { dependency_actions: {
title: 'কর্ম', title: 'কর্ম',
delete_dependency: 'নির্ভরতা মুছুন', delete_dependency: 'মুছুন',
}, },
}, },
empty_state: { empty_state: {
title: 'কোনো নির্ভরতাগুলি নেই', title: 'কোনো সম্পর্ক নেই',
description: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।', description: 'শুরু করতে একটি সম্পর্ক তৈরি করুন',
}, },
}, },
@@ -248,12 +249,16 @@ export const bn: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'কোন enum মান সংজ্ঞায়িত নেই',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -267,8 +272,14 @@ export const bn: LanguageTranslation = {
show_all: 'সব দেখান', show_all: 'সব দেখান',
undo: 'পূর্বাবস্থায় ফিরুন', undo: 'পূর্বাবস্থায় ফিরুন',
redo: 'পুনরায় করুন', redo: 'পুনরায় করুন',
reorder_diagram: 'ডায়াগ্রাম পুনর্বিন্যাস করুন', reorder_diagram: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন', highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -305,7 +316,7 @@ export const bn: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'চিত্র খুলুন', title: 'ডেটাবেস খুলুন',
description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।', description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।',
table_columns: { table_columns: {
name: 'নাম', name: 'নাম',
@@ -315,6 +326,12 @@ export const bn: LanguageTranslation = {
}, },
cancel: 'বাতিল করুন', cancel: 'বাতিল করুন',
open: 'খুলুন', open: 'খুলুন',
diagram_actions: {
open: 'খুলুন',
duplicate: 'ডুপ্লিকেট',
delete: 'মুছুন',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -400,6 +417,13 @@ export const bn: LanguageTranslation = {
cancel: 'বাতিল করুন', cancel: 'বাতিল করুন',
confirm: 'পরিবর্তন করুন', confirm: 'পরিবর্তন করুন',
}, },
create_table_schema_dialog: {
title: 'নতুন স্কিমা তৈরি করুন',
description:
'এখনও কোনো স্কিমা নেই। আপনার টেবিলগুলি সংগঠিত করতে আপনার প্রথম স্কিমা তৈরি করুন।',
create: 'তৈরি করুন',
cancel: 'বাতিল করুন',
},
star_us_dialog: { star_us_dialog: {
title: 'আমাদের উন্নত করতে সাহায্য করুন!', title: 'আমাদের উন্নত করতে সাহায্য করুন!',
@@ -456,6 +480,7 @@ export const bn: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'নতুন টেবিল', new_table: 'নতুন টেবিল',
new_view: 'নতুন ভিউ',
new_relationship: 'নতুন সম্পর্ক', new_relationship: 'নতুন সম্পর্ক',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -477,6 +502,9 @@ export const bn: LanguageTranslation = {
language_select: { language_select: {
change_language: 'ভাষা পরিবর্তন করুন', change_language: 'ভাষা পরিবর্তন করুন',
}, },
on: 'চালু',
off: 'বন্ধ',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const de: LanguageTranslation = { export const de: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Neu',
browse: 'Durchsuchen',
tables: 'Tabellen',
refs: 'Refs',
areas: 'Bereiche',
dependencies: 'Abhängigkeiten',
custom_types: 'Benutzerdefinierte Typen',
},
menu: { menu: {
file: { actions: {
file: 'Datei', actions: 'Aktionen',
new: 'Neu', new: 'Neu...',
open: 'Öffnen', browse: 'Durchsuchen...',
save: 'Speichern', save: 'Speichern',
import: 'Datenbank importieren', import: 'Datenbank importieren',
export_sql: 'SQL exportieren', export_sql: 'SQL exportieren',
export_as: 'Exportieren als', export_as: 'Exportieren als',
delete_diagram: 'Diagramm löschen', delete_diagram: 'Löschen',
exit: 'Beenden',
}, },
edit: { edit: {
edit: 'Bearbeiten', edit: 'Bearbeiten',
@@ -26,7 +34,10 @@ export const de: LanguageTranslation = {
hide_sidebar: 'Seitenleiste ausblenden', hide_sidebar: 'Seitenleiste ausblenden',
hide_cardinality: 'Kardinalität ausblenden', hide_cardinality: 'Kardinalität ausblenden',
show_cardinality: 'Kardinalität anzeigen', show_cardinality: 'Kardinalität anzeigen',
hide_field_attributes: 'Feldattribute ausblenden',
show_field_attributes: 'Feldattribute anzeigen',
zoom_on_scroll: 'Zoom beim Scrollen', zoom_on_scroll: 'Zoom beim Scrollen',
show_views: 'Datenbankansichten',
theme: 'Stil', theme: 'Stil',
show_dependencies: 'Abhängigkeiten anzeigen', show_dependencies: 'Abhängigkeiten anzeigen',
hide_dependencies: 'Abhängigkeiten ausblenden', hide_dependencies: 'Abhängigkeiten ausblenden',
@@ -64,22 +75,13 @@ export const de: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Diagramm neu anordnen', title: 'Diagramm automatisch anordnen',
description: description:
'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?', 'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?',
reorder: 'Neu anordnen', reorder: 'Automatisch anordnen',
cancel: 'Abbrechen', cancel: 'Abbrechen',
}, },
multiple_schemas_alert: {
title: 'Mehrere Schemas',
description:
'{{schemasCount}} Schemas in diesem Diagramm. Derzeit angezeigt: {{formattedSchemas}}.',
dont_show_again: 'Nicht erneut anzeigen',
change_schema: 'Schema ändern',
none: 'Keine',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Kopieren fehlgeschlagen', title: 'Kopieren fehlgeschlagen',
@@ -115,14 +117,11 @@ export const de: LanguageTranslation = {
copied: 'Kopiert!', copied: 'Kopiert!',
side_panel: { side_panel: {
schema: 'Schema:',
filter_by_schema: 'Nach Schema filtern',
search_schema: 'Schema suchen...',
no_schemas_found: 'Keine Schemas gefunden.',
view_all_options: 'Alle Optionen anzeigen...', view_all_options: 'Alle Optionen anzeigen...',
tables_section: { tables_section: {
tables: 'Tabellen', tables: 'Tabellen',
add_table: 'Tabelle hinzufügen', add_table: 'Tabelle hinzufügen',
add_view: 'Ansicht hinzufügen',
filter: 'Filter', filter: 'Filter',
collapse: 'Alle einklappen', collapse: 'Alle einklappen',
// TODO: Translate // TODO: Translate
@@ -148,16 +147,23 @@ export const de: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Feldattribute', title: 'Feldattribute',
unique: 'Eindeutig', unique: 'Eindeutig',
auto_increment: 'Automatisch hochzählen',
comments: 'Kommentare', comments: 'Kommentare',
no_comments: 'Keine Kommentare', no_comments: 'Keine Kommentare',
delete_field: 'Feld löschen', delete_field: 'Feld löschen',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Präzision',
scale: 'Skalierung',
}, },
index_actions: { index_actions: {
title: 'Indexattribute', title: 'Indexattribute',
name: 'Name', name: 'Name',
unique: 'Eindeutig', unique: 'Eindeutig',
index_type: 'Indextyp',
delete_index: 'Index löschen', delete_index: 'Index löschen',
}, },
table_actions: { table_actions: {
@@ -174,32 +180,26 @@ export const de: LanguageTranslation = {
description: 'Erstellen Sie eine Tabelle, um zu beginnen', description: 'Erstellen Sie eine Tabelle, um zu beginnen',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Beziehungen', refs: 'Refs',
filter: 'Filter', filter: 'Filter',
add_relationship: 'Beziehung hinzufügen',
collapse: 'Alle einklappen', collapse: 'Alle einklappen',
add_relationship: 'Beziehung hinzufügen',
relationships: 'Beziehungen',
dependencies: 'Abhängigkeiten',
relationship: { relationship: {
relationship: 'Beziehung',
primary: 'Primäre Tabelle', primary: 'Primäre Tabelle',
foreign: 'Referenzierte Tabelle', foreign: 'Referenzierte Tabelle',
cardinality: 'Kardinalität', cardinality: 'Kardinalität',
delete_relationship: 'Beziehung löschen', delete_relationship: 'Löschen',
relationship_actions: { relationship_actions: {
title: 'Aktionen', title: 'Aktionen',
delete_relationship: 'Beziehung löschen', delete_relationship: 'Löschen',
}, },
}, },
empty_state: {
title: 'Keine Beziehungen',
description:
'Erstellen Sie eine Beziehung, um Tabellen zu verbinden',
},
},
dependencies_section: {
dependencies: 'Abhängigkeiten',
filter: 'Filter',
collapse: 'Alle einklappen',
dependency: { dependency: {
dependency: 'Abhängigkeit',
table: 'Tabelle', table: 'Tabelle',
dependent_table: 'Abhängige Ansicht', dependent_table: 'Abhängige Ansicht',
delete_dependency: 'Löschen', delete_dependency: 'Löschen',
@@ -209,8 +209,8 @@ export const de: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Keine Abhängigkeiten', title: 'Keine Beziehungen',
description: 'Erstellen Sie eine Ansicht, um zu beginnen', description: 'Erstellen Sie eine Beziehung, um zu beginnen',
}, },
}, },
@@ -250,12 +250,16 @@ export const de: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Keine Enum-Werte definiert',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -269,8 +273,15 @@ export const de: LanguageTranslation = {
show_all: 'Alle anzeigen', show_all: 'Alle anzeigen',
undo: 'Rückgängig', undo: 'Rückgängig',
redo: 'Wiederholen', redo: 'Wiederholen',
reorder_diagram: 'Diagramm neu anordnen', reorder_diagram: 'Diagramm automatisch anordnen',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Überlappende Tabellen hervorheben', highlight_overlapping_tables: 'Überlappende Tabellen hervorheben',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -308,7 +319,7 @@ export const de: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Diagramm öffnen', title: 'Datenbank öffnen',
description: 'Wählen Sie ein Diagramm aus der Liste unten aus.', description: 'Wählen Sie ein Diagramm aus der Liste unten aus.',
table_columns: { table_columns: {
name: 'Name', name: 'Name',
@@ -318,6 +329,12 @@ export const de: LanguageTranslation = {
}, },
cancel: 'Abbrechen', cancel: 'Abbrechen',
open: 'Öffnen', open: 'Öffnen',
diagram_actions: {
open: 'Öffnen',
duplicate: 'Duplizieren',
delete: 'Löschen',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -403,6 +420,13 @@ export const de: LanguageTranslation = {
cancel: 'Abbrechen', cancel: 'Abbrechen',
confirm: 'Ändern', confirm: 'Ändern',
}, },
create_table_schema_dialog: {
title: 'Neues Schema erstellen',
description:
'Es existieren noch keine Schemas. Erstellen Sie Ihr erstes Schema, um Ihre Tabellen zu organisieren.',
create: 'Erstellen',
cancel: 'Abbrechen',
},
star_us_dialog: { star_us_dialog: {
title: 'Hilf uns, uns zu verbessern!', title: 'Hilf uns, uns zu verbessern!',
@@ -459,6 +483,7 @@ export const de: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Neue Tabelle', new_table: 'Neue Tabelle',
new_view: 'Neue Ansicht',
new_relationship: 'Neue Beziehung', new_relationship: 'Neue Beziehung',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -481,6 +506,9 @@ export const de: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Sprache', change_language: 'Sprache',
}, },
on: 'Ein',
off: 'Aus',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata } from '../types';
export const en = { export const en = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'New',
browse: 'Browse',
tables: 'Tables',
refs: 'Refs',
areas: 'Areas',
dependencies: 'Dependencies',
custom_types: 'Custom Types',
},
menu: { menu: {
file: { actions: {
file: 'File', actions: 'Actions',
new: 'New', new: 'New...',
open: 'Open', browse: 'Browse...',
save: 'Save', save: 'Save',
import: 'Import', import: 'Import',
export_sql: 'Export SQL', export_sql: 'Export SQL',
export_as: 'Export as', export_as: 'Export as',
delete_diagram: 'Delete Diagram', delete_diagram: 'Delete',
exit: 'Exit',
}, },
edit: { edit: {
edit: 'Edit', edit: 'Edit',
@@ -26,7 +34,10 @@ export const en = {
hide_sidebar: 'Hide Sidebar', hide_sidebar: 'Hide Sidebar',
hide_cardinality: 'Hide Cardinality', hide_cardinality: 'Hide Cardinality',
show_cardinality: 'Show Cardinality', show_cardinality: 'Show Cardinality',
hide_field_attributes: 'Hide Field Attributes',
show_field_attributes: 'Show Field Attributes',
zoom_on_scroll: 'Zoom on Scroll', zoom_on_scroll: 'Zoom on Scroll',
show_views: 'Database Views',
theme: 'Theme', theme: 'Theme',
show_dependencies: 'Show Dependencies', show_dependencies: 'Show Dependencies',
hide_dependencies: 'Hide Dependencies', hide_dependencies: 'Hide Dependencies',
@@ -62,22 +73,13 @@ export const en = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Reorder Diagram', title: 'Auto Arrange Diagram',
description: description:
'This action will rearrange all tables in the diagram. Do you want to continue?', 'This action will rearrange all tables in the diagram. Do you want to continue?',
reorder: 'Reorder', reorder: 'Auto Arrange',
cancel: 'Cancel', cancel: 'Cancel',
}, },
multiple_schemas_alert: {
title: 'Multiple Schemas',
description:
'{{schemasCount}} schemas in this diagram. Currently displaying: {{formattedSchemas}}.',
dont_show_again: "Don't show again",
change_schema: 'Change',
none: 'none',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Copy failed', title: 'Copy failed',
@@ -112,14 +114,11 @@ export const en = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'Schema:',
filter_by_schema: 'Filter by schema',
search_schema: 'Search schema...',
no_schemas_found: 'No schemas found.',
view_all_options: 'View all Options...', view_all_options: 'View all Options...',
tables_section: { tables_section: {
tables: 'Tables', tables: 'Tables',
add_table: 'Add Table', add_table: 'Add Table',
add_view: 'Add View',
filter: 'Filter', filter: 'Filter',
collapse: 'Collapse All', collapse: 'Collapse All',
clear: 'Clear Filter', clear: 'Clear Filter',
@@ -143,15 +142,21 @@ export const en = {
field_actions: { field_actions: {
title: 'Field Attributes', title: 'Field Attributes',
unique: 'Unique', unique: 'Unique',
auto_increment: 'Auto Increment',
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Precision',
scale: 'Scale',
comments: 'Comments', comments: 'Comments',
no_comments: 'No comments', no_comments: 'No comments',
default_value: 'Default Value',
no_default: 'No default',
delete_field: 'Delete Field', delete_field: 'Delete Field',
}, },
index_actions: { index_actions: {
title: 'Index Attributes', title: 'Index Attributes',
name: 'Name', name: 'Name',
unique: 'Unique', unique: 'Unique',
index_type: 'Index Type',
delete_index: 'Delete Index', delete_index: 'Delete Index',
}, },
table_actions: { table_actions: {
@@ -168,12 +173,15 @@ export const en = {
description: 'Create a table to get started', description: 'Create a table to get started',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relationships', refs: 'Refs',
filter: 'Filter', filter: 'Filter',
add_relationship: 'Add Relationship',
collapse: 'Collapse All', collapse: 'Collapse All',
add_relationship: 'Add Relationship',
relationships: 'Relationships',
dependencies: 'Dependencies',
relationship: { relationship: {
relationship: 'Relationship',
primary: 'Primary Table', primary: 'Primary Table',
foreign: 'Referenced Table', foreign: 'Referenced Table',
cardinality: 'Cardinality', cardinality: 'Cardinality',
@@ -183,16 +191,8 @@ export const en = {
delete_relationship: 'Delete', delete_relationship: 'Delete',
}, },
}, },
empty_state: {
title: 'No relationships',
description: 'Create a relationship to connect tables',
},
},
dependencies_section: {
dependencies: 'Dependencies',
filter: 'Filter',
collapse: 'Collapse All',
dependency: { dependency: {
dependency: 'Dependency',
table: 'Table', table: 'Table',
dependent_table: 'Dependent View', dependent_table: 'Dependent View',
delete_dependency: 'Delete', delete_dependency: 'Delete',
@@ -202,8 +202,8 @@ export const en = {
}, },
}, },
empty_state: { empty_state: {
title: 'No dependencies', title: 'No relationships',
description: 'Create a view to get started', description: 'Create a relationship to get started',
}, },
}, },
@@ -242,11 +242,15 @@ export const en = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'No enum values defined',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
clear_field_highlight: 'Clear Highlight',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
@@ -261,8 +265,12 @@ export const en = {
show_all: 'Show All', show_all: 'Show All',
undo: 'Undo', undo: 'Undo',
redo: 'Redo', redo: 'Redo',
reorder_diagram: 'Reorder Diagram', reorder_diagram: 'Auto Arrange Diagram',
highlight_overlapping_tables: 'Highlight Overlapping Tables', highlight_overlapping_tables: 'Highlight Overlapping Tables',
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -299,7 +307,7 @@ export const en = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Open Diagram', title: 'Open Database',
description: 'Select a diagram to open from the list below.', description: 'Select a diagram to open from the list below.',
table_columns: { table_columns: {
name: 'Name', name: 'Name',
@@ -309,6 +317,12 @@ export const en = {
}, },
cancel: 'Cancel', cancel: 'Cancel',
open: 'Open', open: 'Open',
diagram_actions: {
open: 'Open',
duplicate: 'Duplicate',
delete: 'Delete',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -394,6 +408,14 @@ export const en = {
confirm: 'Change', confirm: 'Change',
}, },
create_table_schema_dialog: {
title: 'Create New Schema',
description:
'No schemas exist yet. Create your first schema to organize your tables.',
create: 'Create',
cancel: 'Cancel',
},
star_us_dialog: { star_us_dialog: {
title: 'Help us improve!', title: 'Help us improve!',
description: description:
@@ -448,6 +470,7 @@ export const en = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'New Table', new_table: 'New Table',
new_view: 'New View',
new_relationship: 'New Relationship', new_relationship: 'New Relationship',
new_area: 'New Area', new_area: 'New Area',
}, },
@@ -468,6 +491,9 @@ export const en = {
language_select: { language_select: {
change_language: 'Language', change_language: 'Language',
}, },
on: 'On',
off: 'Off',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const es: LanguageTranslation = { export const es: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Nuevo',
browse: 'Examinar',
tables: 'Tablas',
refs: 'Refs',
areas: 'Áreas',
dependencies: 'Dependencias',
custom_types: 'Tipos Personalizados',
},
menu: { menu: {
file: { actions: {
file: 'Archivo', actions: 'Acciones',
new: 'Nuevo', new: 'Nuevo...',
open: 'Abrir', browse: 'Examinar...',
save: 'Guardar', save: 'Guardar',
import: 'Importar Base de Datos', import: 'Importar Base de Datos',
export_sql: 'Exportar SQL', export_sql: 'Exportar SQL',
export_as: 'Exportar como', export_as: 'Exportar como',
delete_diagram: 'Eliminar Diagrama', delete_diagram: 'Eliminar',
exit: 'Salir',
}, },
edit: { edit: {
edit: 'Editar', edit: 'Editar',
@@ -24,9 +32,12 @@ export const es: LanguageTranslation = {
view: 'Ver', view: 'Ver',
hide_cardinality: 'Ocultar Cardinalidad', hide_cardinality: 'Ocultar Cardinalidad',
show_cardinality: 'Mostrar Cardinalidad', show_cardinality: 'Mostrar Cardinalidad',
show_field_attributes: 'Mostrar Atributos de Campo',
hide_field_attributes: 'Ocultar Atributos de Campo',
show_sidebar: 'Mostrar Barra Lateral', show_sidebar: 'Mostrar Barra Lateral',
hide_sidebar: 'Ocultar Barra Lateral', hide_sidebar: 'Ocultar Barra Lateral',
zoom_on_scroll: 'Zoom al Desplazarse', zoom_on_scroll: 'Zoom al Desplazarse',
show_views: 'Vistas de Base de Datos',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Mostrar dependencias', show_dependencies: 'Mostrar dependencias',
hide_dependencies: 'Ocultar dependencias', hide_dependencies: 'Ocultar dependencias',
@@ -63,10 +74,10 @@ export const es: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Reordenar Diagrama', title: 'Organizar Diagrama Automáticamente',
description: description:
'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?', 'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?',
reorder: 'Reordenar', reorder: 'Organizar Automáticamente',
cancel: 'Cancelar', cancel: 'Cancelar',
}, },
@@ -104,14 +115,11 @@ export const es: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'Esquema:',
filter_by_schema: 'Filtrar por esquema',
search_schema: 'Buscar esquema...',
no_schemas_found: 'No se encontraron esquemas.',
view_all_options: 'Ver todas las opciones...', view_all_options: 'Ver todas las opciones...',
tables_section: { tables_section: {
tables: 'Tablas', tables: 'Tablas',
add_table: 'Agregar Tabla', add_table: 'Agregar Tabla',
add_view: 'Agregar Vista',
filter: 'Filtrar', filter: 'Filtrar',
collapse: 'Colapsar Todo', collapse: 'Colapsar Todo',
// TODO: Translate // TODO: Translate
@@ -137,16 +145,23 @@ export const es: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Atributos del Campo', title: 'Atributos del Campo',
unique: 'Único', unique: 'Único',
auto_increment: 'Autoincremento',
comments: 'Comentarios', comments: 'Comentarios',
no_comments: 'Sin comentarios', no_comments: 'Sin comentarios',
delete_field: 'Eliminar Campo', delete_field: 'Eliminar Campo',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Precisión',
scale: 'Escala',
}, },
index_actions: { index_actions: {
title: 'Atributos del Índice', title: 'Atributos del Índice',
name: 'Nombre', name: 'Nombre',
unique: 'Único', unique: 'Único',
index_type: 'Tipo de Índice',
delete_index: 'Eliminar Índice', delete_index: 'Eliminar Índice',
}, },
table_actions: { table_actions: {
@@ -163,14 +178,17 @@ export const es: LanguageTranslation = {
description: 'Crea una tabla para comenzar', description: 'Crea una tabla para comenzar',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relaciones', refs: 'Refs',
add_relationship: 'Agregar Relación',
filter: 'Filtrar', filter: 'Filtrar',
collapse: 'Colapsar Todo', collapse: 'Colapsar Todo',
add_relationship: 'Agregar Relación',
relationships: 'Relaciones',
dependencies: 'Dependencias',
relationship: { relationship: {
primary: 'Primaria', relationship: 'Relación',
foreign: 'Foránea', primary: 'Tabla Primaria',
foreign: 'Tabla Referenciada',
cardinality: 'Cardinalidad', cardinality: 'Cardinalidad',
delete_relationship: 'Eliminar', delete_relationship: 'Eliminar',
relationship_actions: { relationship_actions: {
@@ -178,18 +196,10 @@ export const es: LanguageTranslation = {
delete_relationship: 'Eliminar', delete_relationship: 'Eliminar',
}, },
}, },
empty_state: {
title: 'No hay relaciones',
description: 'Crea una relación para conectar tablas',
},
},
dependencies_section: {
dependencies: 'Dependencias',
filter: 'Filtro',
collapse: 'Colapsar todo',
dependency: { dependency: {
dependency: 'Dependencia',
table: 'Tabla', table: 'Tabla',
dependent_table: 'Vista dependiente', dependent_table: 'Vista Dependiente',
delete_dependency: 'Eliminar', delete_dependency: 'Eliminar',
dependency_actions: { dependency_actions: {
title: 'Acciones', title: 'Acciones',
@@ -197,8 +207,8 @@ export const es: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Sin dependencias', title: 'Sin relaciones',
description: 'Crea una vista para comenzar', description: 'Crea una relación para comenzar',
}, },
}, },
@@ -238,12 +248,16 @@ export const es: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'No hay valores de enum definidos',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -257,8 +271,14 @@ export const es: LanguageTranslation = {
show_all: 'Mostrar Todo', show_all: 'Mostrar Todo',
undo: 'Deshacer', undo: 'Deshacer',
redo: 'Rehacer', redo: 'Rehacer',
reorder_diagram: 'Reordenar Diagrama', reorder_diagram: 'Organizar Diagrama Automáticamente',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Resaltar tablas superpuestas', highlight_overlapping_tables: 'Resaltar tablas superpuestas',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -296,7 +316,7 @@ export const es: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Abrir Diagrama', title: 'Abrir Base de Datos',
description: description:
'Selecciona un diagrama para abrir de la lista a continuación.', 'Selecciona un diagrama para abrir de la lista a continuación.',
table_columns: { table_columns: {
@@ -307,6 +327,12 @@ export const es: LanguageTranslation = {
}, },
cancel: 'Cancelar', cancel: 'Cancelar',
open: 'Abrir', open: 'Abrir',
diagram_actions: {
open: 'Abrir',
duplicate: 'Duplicar',
delete: 'Eliminar',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -392,6 +418,13 @@ export const es: LanguageTranslation = {
cancel: 'Cancelar', cancel: 'Cancelar',
confirm: 'Cambiar', confirm: 'Cambiar',
}, },
create_table_schema_dialog: {
title: 'Crear Nuevo Esquema',
description:
'Aún no existen esquemas. Crea tu primer esquema para organizar tus tablas.',
create: 'Crear',
cancel: 'Cancelar',
},
star_us_dialog: { star_us_dialog: {
title: '¡Ayúdanos a mejorar!', title: '¡Ayúdanos a mejorar!',
@@ -401,14 +434,6 @@ export const es: LanguageTranslation = {
confirm: '¡Claro!', confirm: '¡Claro!',
}, },
multiple_schemas_alert: {
title: 'Múltiples Esquemas',
description:
'{{schemasCount}} esquemas en este diagrama. Actualmente mostrando: {{formattedSchemas}}.',
dont_show_again: 'No mostrar de nuevo',
change_schema: 'Cambiar',
none: 'nada',
},
// TODO: Translate // TODO: Translate
export_diagram_dialog: { export_diagram_dialog: {
title: 'Export Diagram', title: 'Export Diagram',
@@ -457,6 +482,7 @@ export const es: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Nueva Tabla', new_table: 'Nueva Tabla',
new_view: 'Nueva Vista',
new_relationship: 'Nueva Relación', new_relationship: 'Nueva Relación',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -479,6 +505,9 @@ export const es: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Idioma', change_language: 'Idioma',
}, },
on: 'Encendido',
off: 'Apagado',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const fr: LanguageTranslation = { export const fr: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Nouveau',
browse: 'Parcourir',
tables: 'Tables',
refs: 'Refs',
areas: 'Zones',
dependencies: 'Dépendances',
custom_types: 'Types Personnalisés',
},
menu: { menu: {
file: { actions: {
file: 'Fichier', actions: 'Actions',
new: 'Nouveau', new: 'Nouveau...',
open: 'Ouvrir', browse: 'Parcourir...',
save: 'Enregistrer', save: 'Enregistrer',
import: 'Importer Base de Données', import: 'Importer Base de Données',
export_sql: 'Exporter SQL', export_sql: 'Exporter SQL',
export_as: 'Exporter en tant que', export_as: 'Exporter en tant que',
delete_diagram: 'Supprimer le Diagramme', delete_diagram: 'Supprimer',
exit: 'Quitter',
}, },
edit: { edit: {
edit: 'Édition', edit: 'Édition',
@@ -26,7 +34,10 @@ export const fr: LanguageTranslation = {
hide_sidebar: 'Cacher la Barre Latérale', hide_sidebar: 'Cacher la Barre Latérale',
hide_cardinality: 'Cacher la Cardinalité', hide_cardinality: 'Cacher la Cardinalité',
show_cardinality: 'Afficher la Cardinalité', show_cardinality: 'Afficher la Cardinalité',
hide_field_attributes: 'Masquer les Attributs de Champ',
show_field_attributes: 'Afficher les Attributs de Champ',
zoom_on_scroll: 'Zoom sur le Défilement', zoom_on_scroll: 'Zoom sur le Défilement',
show_views: 'Vues de Base de Données',
theme: 'Thème', theme: 'Thème',
show_dependencies: 'Afficher les Dépendances', show_dependencies: 'Afficher les Dépendances',
hide_dependencies: 'Masquer les Dépendances', hide_dependencies: 'Masquer les Dépendances',
@@ -62,10 +73,10 @@ export const fr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Réorganiser le Diagramme', title: 'Organiser Automatiquement le Diagramme',
description: description:
'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?', 'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?',
reorder: 'Réorganiser', reorder: 'Organiser Automatiquement',
cancel: 'Annuler', cancel: 'Annuler',
}, },
@@ -103,14 +114,11 @@ export const fr: LanguageTranslation = {
copied: 'Copié !', copied: 'Copié !',
side_panel: { side_panel: {
schema: 'Schéma:',
filter_by_schema: 'Filtrer par schéma',
search_schema: 'Rechercher un schéma...',
no_schemas_found: 'Aucun schéma trouvé.',
view_all_options: 'Voir toutes les Options...', view_all_options: 'Voir toutes les Options...',
tables_section: { tables_section: {
tables: 'Tables', tables: 'Tables',
add_table: 'Ajouter une Table', add_table: 'Ajouter une Table',
add_view: 'Ajouter une Vue',
filter: 'Filtrer', filter: 'Filtrer',
collapse: 'Réduire Tout', collapse: 'Réduire Tout',
clear: 'Effacer le Filtre', clear: 'Effacer le Filtre',
@@ -135,16 +143,23 @@ export const fr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Attributs du Champ', title: 'Attributs du Champ',
unique: 'Unique', unique: 'Unique',
auto_increment: 'Auto-incrément',
comments: 'Commentaires', comments: 'Commentaires',
no_comments: 'Pas de commentaires', no_comments: 'Pas de commentaires',
delete_field: 'Supprimer le Champ', delete_field: 'Supprimer le Champ',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Précision',
scale: 'Échelle',
}, },
index_actions: { index_actions: {
title: "Attributs de l'Index", title: "Attributs de l'Index",
name: 'Nom', name: 'Nom',
unique: 'Unique', unique: 'Unique',
index_type: "Type d'index",
delete_index: "Supprimer l'Index", delete_index: "Supprimer l'Index",
}, },
table_actions: { table_actions: {
@@ -161,12 +176,15 @@ export const fr: LanguageTranslation = {
description: 'Créez une table pour commencer', description: 'Créez une table pour commencer',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relations', refs: 'Refs',
filter: 'Filtrer', filter: 'Filtrer',
add_relationship: 'Ajouter une Relation',
collapse: 'Réduire Tout', collapse: 'Réduire Tout',
add_relationship: 'Ajouter une Relation',
relationships: 'Relations',
dependencies: 'Dépendances',
relationship: { relationship: {
relationship: 'Relation',
primary: 'Table Principale', primary: 'Table Principale',
foreign: 'Table Référencée', foreign: 'Table Référencée',
cardinality: 'Cardinalité', cardinality: 'Cardinalité',
@@ -176,16 +194,8 @@ export const fr: LanguageTranslation = {
delete_relationship: 'Supprimer', delete_relationship: 'Supprimer',
}, },
}, },
empty_state: {
title: 'Aucune relation',
description: 'Créez une relation pour connecter les tables',
},
},
dependencies_section: {
dependencies: 'Dépendances',
filter: 'Filtrer',
collapse: 'Réduire Tout',
dependency: { dependency: {
dependency: 'Dépendance',
table: 'Table', table: 'Table',
dependent_table: 'Vue Dépendante', dependent_table: 'Vue Dépendante',
delete_dependency: 'Supprimer', delete_dependency: 'Supprimer',
@@ -195,8 +205,8 @@ export const fr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Aucune dépendance', title: 'Aucune relation',
description: 'Créez une vue pour commencer', description: 'Créez une relation pour commencer',
}, },
}, },
@@ -236,12 +246,16 @@ export const fr: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: "Aucune valeur d'énumération définie",
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -255,8 +269,14 @@ export const fr: LanguageTranslation = {
show_all: 'Afficher Tout', show_all: 'Afficher Tout',
undo: 'Annuler', undo: 'Annuler',
redo: 'Rétablir', redo: 'Rétablir',
reorder_diagram: 'Réorganiser le Diagramme', reorder_diagram: 'Organiser Automatiquement le Diagramme',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Surligner les tables chevauchées', highlight_overlapping_tables: 'Surligner les tables chevauchées',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -293,7 +313,7 @@ export const fr: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Ouvrir Diagramme', title: 'Ouvrir Base de Données',
description: description:
'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.', 'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.',
table_columns: { table_columns: {
@@ -304,6 +324,12 @@ export const fr: LanguageTranslation = {
}, },
cancel: 'Annuler', cancel: 'Annuler',
open: 'Ouvrir', open: 'Ouvrir',
diagram_actions: {
open: 'Ouvrir',
duplicate: 'Dupliquer',
delete: 'Supprimer',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -341,15 +367,6 @@ export const fr: LanguageTranslation = {
transparent_description: 'Remove background color from image.', transparent_description: 'Remove background color from image.',
}, },
multiple_schemas_alert: {
title: 'Schémas Multiples',
description:
'{{schemasCount}} schémas dans ce diagramme. Actuellement affiché(s) : {{formattedSchemas}}.',
dont_show_again: 'Ne plus afficher',
change_schema: 'Changer',
none: 'Aucun',
},
new_table_schema_dialog: { new_table_schema_dialog: {
title: 'Sélectionner un Schéma', title: 'Sélectionner un Schéma',
description: description:
@@ -372,6 +389,13 @@ export const fr: LanguageTranslation = {
cancel: 'Annuler', cancel: 'Annuler',
confirm: 'Modifier', confirm: 'Modifier',
}, },
create_table_schema_dialog: {
title: 'Créer un Nouveau Schéma',
description:
"Aucun schéma n'existe encore. Créez votre premier schéma pour organiser vos tables.",
create: 'Créer',
cancel: 'Annuler',
},
create_relationship_dialog: { create_relationship_dialog: {
title: 'Créer une Relation', title: 'Créer une Relation',
@@ -454,6 +478,7 @@ export const fr: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Nouvelle Table', new_table: 'Nouvelle Table',
new_view: 'Nouvelle Vue',
new_relationship: 'Nouvelle Relation', new_relationship: 'Nouvelle Relation',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -476,6 +501,9 @@ export const fr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Langue', change_language: 'Langue',
}, },
on: 'Activé',
off: 'Désactivé',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const gu: LanguageTranslation = { export const gu: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'નવું',
browse: 'બ્રાઉજ',
tables: 'ટેબલો',
refs: 'રેફ્સ',
areas: 'ક્ષેત્રો',
dependencies: 'નિર્ભરતાઓ',
custom_types: 'કસ્ટમ ટાઇપ',
},
menu: { menu: {
file: { actions: {
file: 'ફાઇલ', actions: 'ક્રિયાઓ',
new: 'નવું', new: 'નવું...',
open: 'ખોલો', browse: 'બ્રાઉજ કરો...',
save: 'સાચવો', save: 'સાચવો',
import: 'ડેટાબેસ આયાત કરો', import: 'ડેટાબેસ આયાત કરો',
export_sql: 'SQL નિકાસ કરો', export_sql: 'SQL નિકાસ કરો',
export_as: 'રૂપે નિકાસ કરો', export_as: 'રૂપે નિકાસ કરો',
delete_diagram: 'ડાયાગ્રામ કાઢી નાખો', delete_diagram: 'કાઢી નાખો',
exit: 'બહાર જાઓ',
}, },
edit: { edit: {
edit: 'ફેરફાર', edit: 'ફેરફાર',
@@ -26,7 +34,10 @@ export const gu: LanguageTranslation = {
hide_sidebar: 'સાઇડબાર છુપાવો', hide_sidebar: 'સાઇડબાર છુપાવો',
hide_cardinality: 'કાર્ડિનાલિટી છુપાવો', hide_cardinality: 'કાર્ડિનાલિટી છુપાવો',
show_cardinality: 'કાર્ડિનાલિટી બતાવો', show_cardinality: 'કાર્ડિનાલિટી બતાવો',
hide_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ છુપાવો',
show_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ બતાવો',
zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો', zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો',
show_views: 'ડેટાબેઝ વ્યૂઝ',
theme: 'થિમ', theme: 'થિમ',
show_dependencies: 'નિર્ભરતાઓ બતાવો', show_dependencies: 'નિર્ભરતાઓ બતાવો',
hide_dependencies: 'નિર્ભરતાઓ છુપાવો', hide_dependencies: 'નિર્ભરતાઓ છુપાવો',
@@ -64,22 +75,13 @@ export const gu: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'ડાયાગ્રામ ફરી વ્યવસ્થિત કરો', title: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
description: description:
'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?', 'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?',
reorder: 'ફરી વ્યવસ્થિત કરો', reorder: 'ઑટોમેટિક ગોઠવો',
cancel: 'રદ કરો', cancel: 'રદ કરો',
}, },
multiple_schemas_alert: {
title: 'કઈંક વધારે સ્કીમા',
description:
'{{schemasCount}} સ્કીમા આ ડાયાગ્રામમાં છે. હાલમાં દર્શાવેલ છે: {{formattedSchemas}}.',
dont_show_again: 'ફરીથી ન બતાવો',
change_schema: 'બદલો',
none: 'કઈ નહીં',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'નકલ નિષ્ફળ', title: 'નકલ નિષ્ફળ',
@@ -114,14 +116,11 @@ export const gu: LanguageTranslation = {
copied: 'નકલ થયું!', copied: 'નકલ થયું!',
side_panel: { side_panel: {
schema: 'સ્કીમા:',
filter_by_schema: 'સ્કીમા દ્વારા ફિલ્ટર કરો',
search_schema: 'સ્કીમા શોધો...',
no_schemas_found: 'કોઈ સ્કીમા મળ્યા નથી.',
view_all_options: 'બધા વિકલ્પો જુઓ...', view_all_options: 'બધા વિકલ્પો જુઓ...',
tables_section: { tables_section: {
tables: 'ટેબલ્સ', tables: 'ટેબલ્સ',
add_table: 'ટેબલ ઉમેરો', add_table: 'ટેબલ ઉમેરો',
add_view: 'વ્યૂ ઉમેરો',
filter: 'ફિલ્ટર', filter: 'ફિલ્ટર',
collapse: 'બધાને સકુચિત કરો', collapse: 'બધાને સકુચિત કરો',
// TODO: Translate // TODO: Translate
@@ -148,16 +147,23 @@ export const gu: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ફીલ્ડ લક્ષણો', title: 'ફીલ્ડ લક્ષણો',
unique: 'અદ્વિતીય', unique: 'અદ્વિતીય',
auto_increment: 'ઑટો ઇન્ક્રિમેન્ટ',
comments: 'ટિપ્પણીઓ', comments: 'ટિપ્પણીઓ',
no_comments: 'કોઈ ટિપ્પણીઓ નથી', no_comments: 'કોઈ ટિપ્પણીઓ નથી',
delete_field: 'ફીલ્ડ કાઢી નાખો', delete_field: 'ફીલ્ડ કાઢી નાખો',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'ચોકસાઈ',
scale: 'માપ',
}, },
index_actions: { index_actions: {
title: 'ઇન્ડેક્સ લક્ષણો', title: 'ઇન્ડેક્સ લક્ષણો',
name: 'નામ', name: 'નામ',
unique: 'અદ્વિતીય', unique: 'અદ્વિતીય',
index_type: 'ઇન્ડેક્સ પ્રકાર',
delete_index: 'ઇન્ડેક્સ કાઢી નાખો', delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
}, },
table_actions: { table_actions: {
@@ -174,14 +180,17 @@ export const gu: LanguageTranslation = {
description: 'શરૂ કરવા માટે એક ટેબલ બનાવો', description: 'શરૂ કરવા માટે એક ટેબલ બનાવો',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'સંબંધો', refs: 'રેફ્સ',
filter: 'ફિલ્ટર', filter: 'ફિલ્ટર',
add_relationship: 'સંબંધ ઉમેરો',
collapse: 'બધાને સકુચિત કરો', collapse: 'બધાને સકુચિત કરો',
add_relationship: 'સંબંધ ઉમેરો',
relationships: 'સંબંધો',
dependencies: 'નિર્ભરતાઓ',
relationship: { relationship: {
relationship: 'સંબંધ',
primary: 'પ્રાથમિક ટેબલ', primary: 'પ્રાથમિક ટેબલ',
foreign: 'સંદર્ભ ટેબલ', foreign: 'સંદર્ભિત ટેબલ',
cardinality: 'કાર્ડિનાલિટી', cardinality: 'કાર્ડિનાલિટી',
delete_relationship: 'કાઢી નાખો', delete_relationship: 'કાઢી નાખો',
relationship_actions: { relationship_actions: {
@@ -189,27 +198,19 @@ export const gu: LanguageTranslation = {
delete_relationship: 'કાઢી નાખો', delete_relationship: 'કાઢી નાખો',
}, },
}, },
empty_state: {
title: 'કોઈ સંબંધો નથી',
description: 'ટેબલ્સ કનેક્ટ કરવા માટે એક સંબંધ બનાવો',
},
},
dependencies_section: {
dependencies: 'નિર્ભરતાઓ',
filter: 'ફિલ્ટર',
collapse: 'સિકોડો',
dependency: { dependency: {
dependency: 'નિર્ભરતા',
table: 'ટેબલ', table: 'ટેબલ',
dependent_table: 'આધાર રાખેલું ટેબલ', dependent_table: 'નિર્ભરશીલ વ્યૂ',
delete_dependency: 'નિર્ભરતા કાઢી નાખો', delete_dependency: 'કાઢી નાખો',
dependency_actions: { dependency_actions: {
title: 'ક્રિયાઓ', title: 'ક્રિયાઓ',
delete_dependency: 'નિર્ભરતા કાઢી નાખો', delete_dependency: 'કાઢી નાખો',
}, },
}, },
empty_state: { empty_state: {
title: 'કોઈ નિર્ભરતાઓ નથી', title: 'કોઈ સંબંધો નથી',
description: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.', description: 'શરૂ કરવા માટે એક સંબંધ બનાવો',
}, },
}, },
@@ -249,12 +250,16 @@ export const gu: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'કોઈ enum મૂલ્યો વ્યાખ્યાયિત નથી',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -268,8 +273,14 @@ export const gu: LanguageTranslation = {
show_all: 'બધું બતાવો', show_all: 'બધું બતાવો',
undo: 'અનડુ', undo: 'અનડુ',
redo: 'રીડુ', redo: 'રીડુ',
reorder_diagram: 'ડાયાગ્રામ ફરીથી વ્યવસ્થિત કરો', reorder_diagram: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'ઓવરલેપ કરતો ટેબલ હાઇલાઇટ કરો', highlight_overlapping_tables: 'ઓવરલેપ કરતો ટેબલ હાઇલાઇટ કરો',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -305,7 +316,7 @@ export const gu: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'ડાયાગ્રામ ખોલો', title: 'ડેટાબેસ ખોલો',
description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.', description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.',
table_columns: { table_columns: {
name: 'નામ', name: 'નામ',
@@ -315,6 +326,12 @@ export const gu: LanguageTranslation = {
}, },
cancel: 'રદ કરો', cancel: 'રદ કરો',
open: 'ખોલો', open: 'ખોલો',
diagram_actions: {
open: 'ખોલો',
duplicate: 'ડુપ્લિકેટ',
delete: 'કાઢી નાખો',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -401,6 +418,14 @@ export const gu: LanguageTranslation = {
confirm: 'બદલો', confirm: 'બદલો',
}, },
create_table_schema_dialog: {
title: 'નવું સ્કીમા બનાવો',
description:
'હજી સુધી કોઈ સ્કીમા અસ્તિત્વમાં નથી. તમારા ટેબલ્સ ને વ્યવસ્થિત કરવા માટે તમારું પહેલું સ્કીમા બનાવો.',
create: 'બનાવો',
cancel: 'રદ કરો',
},
star_us_dialog: { star_us_dialog: {
title: 'અમને સુધારવામાં મદદ કરો!', title: 'અમને સુધારવામાં મદદ કરો!',
description: description:
@@ -456,6 +481,7 @@ export const gu: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'નવું ટેબલ', new_table: 'નવું ટેબલ',
new_view: 'નવું વ્યૂ',
new_relationship: 'નવો સંબંધ', new_relationship: 'નવો સંબંધ',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -477,6 +503,9 @@ export const gu: LanguageTranslation = {
language_select: { language_select: {
change_language: 'ભાષા બદલો', change_language: 'ભાષા બદલો',
}, },
on: 'ચાલુ',
off: 'બંધ',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const hi: LanguageTranslation = { export const hi: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'नया',
browse: 'ब्राउज़',
tables: 'टेबल',
refs: 'रेफ्स',
areas: 'क्षेत्र',
dependencies: 'निर्भरताएं',
custom_types: 'कस्टम टाइप',
},
menu: { menu: {
file: { actions: {
file: 'फ़ाइल', actions: 'कार्य',
new: 'नया', new: 'नया...',
open: 'खोलें', browse: 'ब्राउज़ करें...',
save: 'सहेजें', save: 'सहेजें',
import: 'डेटाबेस आयात करें', import: 'डेटाबेस आयात करें',
export_sql: 'SQL निर्यात करें', export_sql: 'SQL निर्यात करें',
export_as: 'के रूप में निर्यात करें', export_as: 'के रूप में निर्यात करें',
delete_diagram: 'आरेख हटाएँ', delete_diagram: 'हटाएँ',
exit: 'बाहर जाएँ',
}, },
edit: { edit: {
edit: 'संपादित करें', edit: 'संपादित करें',
@@ -26,7 +34,10 @@ export const hi: LanguageTranslation = {
hide_sidebar: 'साइडबार छिपाएँ', hide_sidebar: 'साइडबार छिपाएँ',
hide_cardinality: 'कार्डिनैलिटी छिपाएँ', hide_cardinality: 'कार्डिनैलिटी छिपाएँ',
show_cardinality: 'कार्डिनैलिटी दिखाएँ', show_cardinality: 'कार्डिनैलिटी दिखाएँ',
hide_field_attributes: 'फ़ील्ड विशेषताएँ छिपाएँ',
show_field_attributes: 'फ़ील्ड विशेषताएँ दिखाएँ',
zoom_on_scroll: 'स्क्रॉल पर ज़ूम', zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
show_views: 'डेटाबेस व्यू',
theme: 'थीम', theme: 'थीम',
show_dependencies: 'निर्भरता दिखाएँ', show_dependencies: 'निर्भरता दिखाएँ',
hide_dependencies: 'निर्भरता छिपाएँ', hide_dependencies: 'निर्भरता छिपाएँ',
@@ -63,22 +74,13 @@ export const hi: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'आरेख पुनः व्यवस्थित करें', title: 'आरेख स्वचालित व्यवस्थित करें',
description: description:
'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?', 'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?',
reorder: 'पुनः व्यवस्थित करें', reorder: 'स्वचालित व्यवस्थित करें',
cancel: 'रद्द करें', cancel: 'रद्द करें',
}, },
multiple_schemas_alert: {
title: 'एकाधिक स्कीमा',
description:
'{{schemasCount}} स्कीमा इस आरेख में हैं। वर्तमान में प्रदर्शित: {{formattedSchemas}}।',
dont_show_again: 'फिर से न दिखाएँ',
change_schema: 'बदलें',
none: 'कोई नहीं',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'कॉपी असफल', title: 'कॉपी असफल',
@@ -114,14 +116,11 @@ export const hi: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'स्कीमा:',
filter_by_schema: 'स्कीमा द्वारा फ़िल्टर करें',
search_schema: 'स्कीमा खोजें...',
no_schemas_found: 'कोई स्कीमा नहीं मिला।',
view_all_options: 'सभी विकल्प देखें...', view_all_options: 'सभी विकल्प देखें...',
tables_section: { tables_section: {
tables: 'तालिकाएँ', tables: 'तालिकाएँ',
add_table: 'तालिका जोड़ें', add_table: 'तालिका जोड़ें',
add_view: 'व्यू जोड़ें',
filter: 'फ़िल्टर', filter: 'फ़िल्टर',
collapse: 'सभी को संक्षिप्त करें', collapse: 'सभी को संक्षिप्त करें',
// TODO: Translate // TODO: Translate
@@ -147,16 +146,23 @@ export const hi: LanguageTranslation = {
field_actions: { field_actions: {
title: 'फ़ील्ड विशेषताएँ', title: 'फ़ील्ड विशेषताएँ',
unique: 'अद्वितीय', unique: 'अद्वितीय',
auto_increment: 'ऑटो इंक्रीमेंट',
comments: 'टिप्पणियाँ', comments: 'टिप्पणियाँ',
no_comments: 'कोई टिप्पणी नहीं', no_comments: 'कोई टिप्पणी नहीं',
delete_field: 'फ़ील्ड हटाएँ', delete_field: 'फ़ील्ड हटाएँ',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Precision',
scale: 'Scale',
}, },
index_actions: { index_actions: {
title: 'सूचकांक विशेषताएँ', title: 'सूचकांक विशेषताएँ',
name: 'नाम', name: 'नाम',
unique: 'अद्वितीय', unique: 'अद्वितीय',
index_type: 'इंडेक्स प्रकार',
delete_index: 'सूचकांक हटाएँ', delete_index: 'सूचकांक हटाएँ',
}, },
table_actions: { table_actions: {
@@ -173,12 +179,15 @@ export const hi: LanguageTranslation = {
description: 'शुरू करने के लिए एक तालिका बनाएँ', description: 'शुरू करने के लिए एक तालिका बनाएँ',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'संबंध', refs: 'रेफ्स',
filter: 'फ़िल्टर', filter: 'फ़िल्टर',
add_relationship: 'संबंध जोड़ें',
collapse: 'सभी को संक्षिप्त करें', collapse: 'सभी को संक्षिप्त करें',
add_relationship: 'संबंध जोड़ें',
relationships: 'संबंध',
dependencies: 'निर्भरताएँ',
relationship: { relationship: {
relationship: 'संबंध',
primary: 'प्राथमिक तालिका', primary: 'प्राथमिक तालिका',
foreign: 'संदर्भित तालिका', foreign: 'संदर्भित तालिका',
cardinality: 'कार्डिनैलिटी', cardinality: 'कार्डिनैलिटी',
@@ -188,28 +197,19 @@ export const hi: LanguageTranslation = {
delete_relationship: 'हटाएँ', delete_relationship: 'हटाएँ',
}, },
}, },
empty_state: {
title: 'कोई संबंध नहीं',
description:
'तालिकाओं को कनेक्ट करने के लिए एक संबंध बनाएँ',
},
},
dependencies_section: {
dependencies: 'निर्भरताएँ',
filter: 'फ़िल्टर',
collapse: 'सिकोड़ें',
dependency: { dependency: {
dependency: 'निर्भरता',
table: 'तालिका', table: 'तालिका',
dependent_table: 'आश्रित तालिका', dependent_table: 'आश्रित दृश्य',
delete_dependency: 'निर्भरता हटाएँ', delete_dependency: 'हटाएँ',
dependency_actions: { dependency_actions: {
title: 'कार्रवाइयाँ', title: 'क्रियाँ',
delete_dependency: 'निर्भरता हटाएँ', delete_dependency: 'हटाएँ',
}, },
}, },
empty_state: { empty_state: {
title: 'कोई निर्भरता नहीं', title: 'कोई संबंध नहीं',
description: 'इस अनुभाग में कोई निर्भरता उपलब्ध नहीं है।', description: 'शुरू करने के लिए एक संबंध बनाएँ',
}, },
}, },
@@ -249,12 +249,16 @@ export const hi: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'कोई enum मान परिभाषित नहीं',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -268,8 +272,14 @@ export const hi: LanguageTranslation = {
show_all: 'सभी दिखाएँ', show_all: 'सभी दिखाएँ',
undo: 'पूर्ववत करें', undo: 'पूर्ववत करें',
redo: 'पुनः करें', redo: 'पुनः करें',
reorder_diagram: 'आरेख पुनः व्यवस्थित करें', reorder_diagram: 'आरेख स्वचालित व्यवस्थित करें',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'ओवरलैपिंग तालिकाओं को हाइलाइट करें', highlight_overlapping_tables: 'ओवरलैपिंग तालिकाओं को हाइलाइट करें',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -308,7 +318,7 @@ export const hi: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'आरेख खोलें', title: 'डेटाबेस खोलें',
description: 'नीचे दी गई सूची से एक आरेख चुनें।', description: 'नीचे दी गई सूची से एक आरेख चुनें।',
table_columns: { table_columns: {
name: 'नाम', name: 'नाम',
@@ -318,6 +328,12 @@ export const hi: LanguageTranslation = {
}, },
cancel: 'रद्द करें', cancel: 'रद्द करें',
open: 'खोलें', open: 'खोलें',
diagram_actions: {
open: 'खोलें',
duplicate: 'डुप्लिकेट',
delete: 'हटाएं',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -404,6 +420,14 @@ export const hi: LanguageTranslation = {
confirm: 'बदलें', confirm: 'बदलें',
}, },
create_table_schema_dialog: {
title: 'नया स्कीमा बनाएं',
description:
'अभी तक कोई स्कीमा मौजूद नहीं है। अपनी तालिकाओं को व्यवस्थित करने के लिए अपना पहला स्कीमा बनाएं।',
create: 'बनाएं',
cancel: 'रद्द करें',
},
star_us_dialog: { star_us_dialog: {
title: 'हमें सुधारने में मदद करें!', title: 'हमें सुधारने में मदद करें!',
description: description:
@@ -459,6 +483,7 @@ export const hi: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'नई तालिका', new_table: 'नई तालिका',
new_view: 'नया व्यू',
new_relationship: 'नया संबंध', new_relationship: 'नया संबंध',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -481,6 +506,9 @@ export const hi: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा बदलें', change_language: 'भाषा बदलें',
}, },
on: 'चालू',
off: 'बंद',
}, },
}; };

509
src/i18n/locales/hr.ts Normal file
View File

@@ -0,0 +1,509 @@
import type { LanguageMetadata, LanguageTranslation } from '../types';
export const hr: LanguageTranslation = {
translation: {
editor_sidebar: {
new_diagram: 'Novi',
browse: 'Pregledaj',
tables: 'Tablice',
refs: 'Refs',
areas: 'Područja',
dependencies: 'Ovisnosti',
custom_types: 'Prilagođeni Tipovi',
},
menu: {
actions: {
actions: 'Akcije',
new: 'Novi...',
browse: 'Pregledaj...',
save: 'Spremi',
import: 'Uvezi',
export_sql: 'Izvezi SQL',
export_as: 'Izvezi kao',
delete_diagram: 'Izbriši',
},
edit: {
edit: 'Uredi',
undo: 'Poništi',
redo: 'Ponovi',
clear: 'Očisti',
},
view: {
view: 'Prikaz',
show_sidebar: 'Prikaži bočnu traku',
hide_sidebar: 'Sakrij bočnu traku',
hide_cardinality: 'Sakrij kardinalnost',
show_cardinality: 'Prikaži kardinalnost',
hide_field_attributes: 'Sakrij atribute polja',
show_field_attributes: 'Prikaži atribute polja',
zoom_on_scroll: 'Zumiranje pri skrolanju',
show_views: 'Pogledi Baze Podataka',
theme: 'Tema',
show_dependencies: 'Prikaži ovisnosti',
hide_dependencies: 'Sakrij ovisnosti',
show_minimap: 'Prikaži mini kartu',
hide_minimap: 'Sakrij mini kartu',
},
backup: {
backup: 'Sigurnosna kopija',
export_diagram: 'Izvezi dijagram',
restore_diagram: 'Vrati dijagram',
},
help: {
help: 'Pomoć',
docs_website: 'Dokumentacija',
join_discord: 'Pridružite nam se na Discordu',
},
},
delete_diagram_alert: {
title: 'Izbriši dijagram',
description:
'Ova radnja se ne može poništiti. Ovo će trajno izbrisati dijagram.',
cancel: 'Odustani',
delete: 'Izbriši',
},
clear_diagram_alert: {
title: 'Očisti dijagram',
description:
'Ova radnja se ne može poništiti. Ovo će trajno izbrisati sve podatke u dijagramu.',
cancel: 'Odustani',
clear: 'Očisti',
},
reorder_diagram_alert: {
title: 'Automatski preuredi dijagram',
description:
'Ova radnja će preurediti sve tablice u dijagramu. Želite li nastaviti?',
reorder: 'Automatski preuredi',
cancel: 'Odustani',
},
copy_to_clipboard_toast: {
unsupported: {
title: 'Kopiranje neuspješno',
description: 'Međuspremnik nije podržan.',
},
failed: {
title: 'Kopiranje neuspješno',
description: 'Nešto je pošlo po zlu. Molimo pokušajte ponovno.',
},
},
theme: {
system: 'Sustav',
light: 'Svijetla',
dark: 'Tamna',
},
zoom: {
on: 'Uključeno',
off: 'Isključeno',
},
last_saved: 'Zadnje spremljeno',
saved: 'Spremljeno',
loading_diagram: 'Učitavanje dijagrama...',
deselect_all: 'Odznači sve',
select_all: 'Označi sve',
clear: 'Očisti',
show_more: 'Prikaži više',
show_less: 'Prikaži manje',
copy_to_clipboard: 'Kopiraj u međuspremnik',
copied: 'Kopirano!',
side_panel: {
view_all_options: 'Prikaži sve opcije...',
tables_section: {
tables: 'Tablice',
add_table: 'Dodaj tablicu',
add_view: 'Dodaj Pogled',
filter: 'Filtriraj',
collapse: 'Sažmi sve',
clear: 'Očisti filter',
no_results:
'Nema pronađenih tablica koje odgovaraju vašem filteru.',
show_list: 'Prikaži popis tablica',
show_dbml: 'Prikaži DBML uređivač',
table: {
fields: 'Polja',
nullable: 'Može biti null?',
primary_key: 'Primarni ključ',
indexes: 'Indeksi',
comments: 'Komentari',
no_comments: 'Nema komentara',
add_field: 'Dodaj polje',
add_index: 'Dodaj indeks',
index_select_fields: 'Odaberi polja',
no_types_found: 'Nema pronađenih tipova',
field_name: 'Naziv',
field_type: 'Tip',
field_actions: {
title: 'Atributi polja',
unique: 'Jedinstven',
auto_increment: 'Automatsko povećavanje',
character_length: 'Maksimalna dužina',
precision: 'Preciznost',
scale: 'Skala',
comments: 'Komentari',
no_comments: 'Nema komentara',
default_value: 'Zadana vrijednost',
no_default: 'Nema zadane vrijednosti',
delete_field: 'Izbriši polje',
},
index_actions: {
title: 'Atributi indeksa',
name: 'Naziv',
unique: 'Jedinstven',
index_type: 'Vrsta indeksa',
delete_index: 'Izbriši indeks',
},
table_actions: {
title: 'Radnje nad tablicom',
change_schema: 'Promijeni shemu',
add_field: 'Dodaj polje',
add_index: 'Dodaj indeks',
duplicate_table: 'Dupliciraj tablicu',
delete_table: 'Izbriši tablicu',
},
},
empty_state: {
title: 'Nema tablica',
description: 'Stvorite tablicu za početak',
},
},
refs_section: {
refs: 'Refs',
filter: 'Filtriraj',
collapse: 'Sažmi sve',
add_relationship: 'Dodaj vezu',
relationships: 'Veze',
dependencies: 'Ovisnosti',
relationship: {
relationship: 'Veza',
primary: 'Primarna tablica',
foreign: 'Referentna tablica',
cardinality: 'Kardinalnost',
delete_relationship: 'Izbriši',
relationship_actions: {
title: 'Radnje',
delete_relationship: 'Izbriši',
},
},
dependency: {
dependency: 'Ovisnost',
table: 'Tablica',
dependent_table: 'Ovisni pogled',
delete_dependency: 'Izbriši',
dependency_actions: {
title: 'Radnje',
delete_dependency: 'Izbriši',
},
},
empty_state: {
title: 'Nema veze',
description: 'Stvorite vezu za početak',
},
},
areas_section: {
areas: 'Područja',
add_area: 'Dodaj područje',
filter: 'Filtriraj',
clear: 'Očisti filter',
no_results:
'Nema pronađenih područja koja odgovaraju vašem filteru.',
area: {
area_actions: {
title: 'Radnje nad područjem',
edit_name: 'Uredi naziv',
delete_area: 'Izbriši područje',
},
},
empty_state: {
title: 'Nema područja',
description: 'Stvorite područje za početak',
},
},
custom_types_section: {
custom_types: 'Prilagođeni tipovi',
filter: 'Filtriraj',
clear: 'Očisti filter',
no_results:
'Nema pronađenih prilagođenih tipova koji odgovaraju vašem filteru.',
empty_state: {
title: 'Nema prilagođenih tipova',
description:
'Prilagođeni tipovi će se pojaviti ovdje kada budu dostupni u vašoj bazi podataka',
},
custom_type: {
kind: 'Vrsta',
enum_values: 'Enum vrijednosti',
composite_fields: 'Polja',
no_fields: 'Nema definiranih polja',
no_values: 'Nema definiranih enum vrijednosti',
field_name_placeholder: 'Naziv polja',
field_type_placeholder: 'Odaberi tip',
add_field: 'Dodaj polje',
no_fields_tooltip:
'Nema definiranih polja za ovaj prilagođeni tip',
custom_type_actions: {
title: 'Radnje',
highlight_fields: 'Istakni polja',
clear_field_highlight: 'Ukloni isticanje',
delete_custom_type: 'Izbriši',
},
delete_custom_type: 'Izbriši tip',
},
},
},
toolbar: {
zoom_in: 'Uvećaj',
zoom_out: 'Smanji',
save: 'Spremi',
show_all: 'Prikaži sve',
undo: 'Poništi',
redo: 'Ponovi',
reorder_diagram: 'Automatski preuredi dijagram',
highlight_overlapping_tables: 'Istakni preklapajuće tablice',
clear_custom_type_highlight: 'Ukloni isticanje za "{{typeName}}"',
custom_type_highlight_tooltip:
'Isticanje "{{typeName}}" - Kliknite za uklanjanje',
filter: 'Filtriraj tablice',
},
new_diagram_dialog: {
database_selection: {
title: 'Koja je vaša baza podataka?',
description:
'Svaka baza podataka ima svoje jedinstvene značajke i mogućnosti.',
check_examples_long: 'Pogledaj primjere',
check_examples_short: 'Primjeri',
},
import_database: {
title: 'Uvezite svoju bazu podataka',
database_edition: 'Verzija baze podataka:',
step_1: 'Pokrenite ovu skriptu u svojoj bazi podataka:',
step_2: 'Zalijepite rezultat skripte u ovaj dio →',
script_results_placeholder: 'Rezultati skripte ovdje...',
ssms_instructions: {
button_text: 'SSMS upute',
title: 'Upute',
step_1: 'Idite na Tools > Options > Query Results > SQL Server.',
step_2: 'Ako koristite "Results to Grid," promijenite Maximum Characters Retrieved za Non-XML podatke (postavite na 9999999).',
},
instructions_link: 'Trebate pomoć? Pogledajte kako',
check_script_result: 'Provjeri rezultat skripte',
},
cancel: 'Odustani',
import_from_file: 'Uvezi iz datoteke',
back: 'Natrag',
empty_diagram: 'Prazan dijagram',
continue: 'Nastavi',
import: 'Uvezi',
},
open_diagram_dialog: {
title: 'Otvori bazu podataka',
description: 'Odaberite dijagram za otvaranje iz popisa ispod.',
table_columns: {
name: 'Naziv',
created_at: 'Stvoreno',
last_modified: 'Zadnje izmijenjeno',
tables_count: 'Tablice',
},
cancel: 'Odustani',
open: 'Otvori',
diagram_actions: {
open: 'Otvori',
duplicate: 'Dupliciraj',
delete: 'Obriši',
},
},
export_sql_dialog: {
title: 'Izvezi SQL',
description:
'Izvezite shemu vašeg dijagrama u {{databaseType}} skriptu',
close: 'Zatvori',
loading: {
text: 'AI generira SQL za {{databaseType}}...',
description: 'Ovo bi trebalo potrajati do 30 sekundi.',
},
error: {
message:
'Greška pri generiranju SQL skripte. Molimo pokušajte ponovno kasnije ili <0>kontaktirajte nas</0>.',
description:
'Slobodno koristite svoj OPENAI_TOKEN, pogledajte priručnik <0>ovdje</0>.',
},
},
create_relationship_dialog: {
title: 'Kreiraj vezu',
primary_table: 'Primarna tablica',
primary_field: 'Primarno polje',
referenced_table: 'Referentna tablica',
referenced_field: 'Referentno polje',
primary_table_placeholder: 'Odaberi tablicu',
primary_field_placeholder: 'Odaberi polje',
referenced_table_placeholder: 'Odaberi tablicu',
referenced_field_placeholder: 'Odaberi polje',
no_tables_found: 'Nema pronađenih tablica',
no_fields_found: 'Nema pronađenih polja',
create: 'Kreiraj',
cancel: 'Odustani',
},
import_database_dialog: {
title: 'Uvezi u trenutni dijagram',
override_alert: {
title: 'Uvezi bazu podataka',
content: {
alert: 'Uvoz ovog dijagrama će utjecati na postojeće tablice i veze.',
new_tables:
'<bold>{{newTablesNumber}}</bold> novih tablica će biti dodano.',
new_relationships:
'<bold>{{newRelationshipsNumber}}</bold> novih veza će biti stvoreno.',
tables_override:
'<bold>{{tablesOverrideNumber}}</bold> tablica će biti prepisano.',
proceed: 'Želite li nastaviti?',
},
import: 'Uvezi',
cancel: 'Odustani',
},
},
export_image_dialog: {
title: 'Izvezi sliku',
description: 'Odaberite faktor veličine za izvoz:',
scale_1x: '1x Obično',
scale_2x: '2x (Preporučeno)',
scale_3x: '3x',
scale_4x: '4x',
cancel: 'Odustani',
export: 'Izvezi',
advanced_options: 'Napredne opcije',
pattern: 'Uključi pozadinski uzorak',
pattern_description: 'Dodaj suptilni mrežni uzorak u pozadinu.',
transparent: 'Prozirna pozadina',
transparent_description: 'Ukloni boju pozadine iz slike.',
},
new_table_schema_dialog: {
title: 'Odaberi shemu',
description:
'Trenutno je prikazano više shema. Odaberite jednu za novu tablicu.',
cancel: 'Odustani',
confirm: 'Potvrdi',
},
update_table_schema_dialog: {
title: 'Promijeni shemu',
description: 'Ažuriraj shemu tablice "{{tableName}}"',
cancel: 'Odustani',
confirm: 'Promijeni',
},
create_table_schema_dialog: {
title: 'Stvori novu shemu',
description:
'Još ne postoje sheme. Stvorite svoju prvu shemu za organiziranje tablica.',
create: 'Stvori',
cancel: 'Odustani',
},
star_us_dialog: {
title: 'Pomozite nam da se poboljšamo!',
description:
'Želite li nam dati zvjezdicu na GitHubu? Samo je jedan klik!',
close: 'Ne sada',
confirm: 'Naravno!',
},
export_diagram_dialog: {
title: 'Izvezi dijagram',
description: 'Odaberite format za izvoz:',
format_json: 'JSON',
cancel: 'Odustani',
export: 'Izvezi',
error: {
title: 'Greška pri izvozu dijagrama',
description:
'Nešto je pošlo po zlu. Trebate pomoć? support@chartdb.io',
},
},
import_diagram_dialog: {
title: 'Uvezi dijagram',
description: 'Uvezite dijagram iz JSON datoteke.',
cancel: 'Odustani',
import: 'Uvezi',
error: {
title: 'Greška pri uvozu dijagrama',
description:
'JSON dijagrama je nevažeći. Molimo provjerite JSON i pokušajte ponovno. Trebate pomoć? support@chartdb.io',
},
},
import_dbml_dialog: {
example_title: 'Uvezi primjer DBML-a',
title: 'Uvezi DBML',
description: 'Uvezite shemu baze podataka iz DBML formata.',
import: 'Uvezi',
cancel: 'Odustani',
skip_and_empty: 'Preskoči i isprazni',
show_example: 'Prikaži primjer',
error: {
title: 'Greška pri uvozu DBML-a',
description:
'Neuspješno parsiranje DBML-a. Molimo provjerite sintaksu.',
},
},
relationship_type: {
one_to_one: 'Jedan na jedan',
one_to_many: 'Jedan na više',
many_to_one: 'Više na jedan',
many_to_many: 'Više na više',
},
canvas_context_menu: {
new_table: 'Nova tablica',
new_view: 'Novi Pogled',
new_relationship: 'Nova veza',
new_area: 'Novo područje',
},
table_node_context_menu: {
edit_table: 'Uredi tablicu',
duplicate_table: 'Dupliciraj tablicu',
delete_table: 'Izbriši tablicu',
add_relationship: 'Dodaj vezu',
},
snap_to_grid_tooltip: 'Priljepljivanje na mrežu (Drži {{key}})',
tool_tips: {
double_click_to_edit: 'Dvostruki klik za uređivanje',
},
language_select: {
change_language: 'Jezik',
},
on: 'Uključeno',
off: 'Isključeno',
},
};
export const hrMetadata: LanguageMetadata = {
name: 'Croatian',
nativeName: 'Hrvatski',
code: 'hr',
};

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const id_ID: LanguageTranslation = { export const id_ID: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Baru',
browse: 'Jelajahi',
tables: 'Tabel',
refs: 'Refs',
areas: 'Area',
dependencies: 'Ketergantungan',
custom_types: 'Tipe Kustom',
},
menu: { menu: {
file: { actions: {
file: 'Berkas', actions: 'Aksi',
new: 'Buat Baru', new: 'Baru...',
open: 'Buka', browse: 'Jelajahi...',
save: 'Simpan', save: 'Simpan',
import: 'Impor Database', import: 'Impor Database',
export_sql: 'Ekspor SQL', export_sql: 'Ekspor SQL',
export_as: 'Ekspor Sebagai', export_as: 'Ekspor Sebagai',
delete_diagram: 'Hapus Diagram', delete_diagram: 'Hapus',
exit: 'Keluar',
}, },
edit: { edit: {
edit: 'Ubah', edit: 'Ubah',
@@ -26,7 +34,10 @@ export const id_ID: LanguageTranslation = {
hide_sidebar: 'Sembunyikan Sidebar', hide_sidebar: 'Sembunyikan Sidebar',
hide_cardinality: 'Sembunyikan Kardinalitas', hide_cardinality: 'Sembunyikan Kardinalitas',
show_cardinality: 'Tampilkan Kardinalitas', show_cardinality: 'Tampilkan Kardinalitas',
hide_field_attributes: 'Sembunyikan Atribut Kolom',
show_field_attributes: 'Tampilkan Atribut Kolom',
zoom_on_scroll: 'Perbesar saat Scroll', zoom_on_scroll: 'Perbesar saat Scroll',
show_views: 'Tampilan Database',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Tampilkan Dependensi', show_dependencies: 'Tampilkan Dependensi',
hide_dependencies: 'Sembunyikan Dependensi', hide_dependencies: 'Sembunyikan Dependensi',
@@ -63,22 +74,13 @@ export const id_ID: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Atur Ulang Diagram', title: 'Atur Otomatis Diagram',
description: description:
'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?', 'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?',
reorder: 'Atur Ulang', reorder: 'Atur Otomatis',
cancel: 'Batal', cancel: 'Batal',
}, },
multiple_schemas_alert: {
title: 'Schema Lebih dari satu',
description:
'{{schemasCount}} schema di diagram ini. Sedang ditampilkan: {{formattedSchemas}}.',
dont_show_again: 'Jangan tampilkan lagi',
change_schema: 'Ubah',
none: 'Tidak ada',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Gagal menyalin', title: 'Gagal menyalin',
@@ -113,14 +115,11 @@ export const id_ID: LanguageTranslation = {
copied: 'Tersalin!', copied: 'Tersalin!',
side_panel: { side_panel: {
schema: 'Skema:',
filter_by_schema: 'Saring berdasarkan skema',
search_schema: 'Cari skema...',
no_schemas_found: 'Tidak ada skema yang ditemukan.',
view_all_options: 'Tampilkan Semua Pilihan...', view_all_options: 'Tampilkan Semua Pilihan...',
tables_section: { tables_section: {
tables: 'Tabel', tables: 'Tabel',
add_table: 'Tambah Tabel', add_table: 'Tambah Tabel',
add_view: 'Tambah Tampilan',
filter: 'Saring', filter: 'Saring',
collapse: 'Lipat Semua', collapse: 'Lipat Semua',
// TODO: Translate // TODO: Translate
@@ -146,16 +145,23 @@ export const id_ID: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Atribut Kolom', title: 'Atribut Kolom',
unique: 'Unik', unique: 'Unik',
auto_increment: 'Kenaikan Otomatis',
comments: 'Komentar', comments: 'Komentar',
no_comments: 'Tidak ada komentar', no_comments: 'Tidak ada komentar',
delete_field: 'Hapus Kolom', delete_field: 'Hapus Kolom',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Presisi',
scale: 'Skala',
}, },
index_actions: { index_actions: {
title: 'Atribut Indeks', title: 'Atribut Indeks',
name: 'Nama', name: 'Nama',
unique: 'Unik', unique: 'Unik',
index_type: 'Tipe Indeks',
delete_index: 'Hapus Indeks', delete_index: 'Hapus Indeks',
}, },
table_actions: { table_actions: {
@@ -172,12 +178,15 @@ export const id_ID: LanguageTranslation = {
description: 'Buat tabel untuk memulai', description: 'Buat tabel untuk memulai',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Hubungan', refs: 'Refs',
filter: 'Saring', filter: 'Saring',
add_relationship: 'Tambah Hubungan',
collapse: 'Lipat Semua', collapse: 'Lipat Semua',
add_relationship: 'Tambah Hubungan',
relationships: 'Hubungan',
dependencies: 'Dependensi',
relationship: { relationship: {
relationship: 'Hubungan',
primary: 'Tabel Primer', primary: 'Tabel Primer',
foreign: 'Tabel Referensi', foreign: 'Tabel Referensi',
cardinality: 'Kardinalitas', cardinality: 'Kardinalitas',
@@ -187,16 +196,8 @@ export const id_ID: LanguageTranslation = {
delete_relationship: 'Hapus', delete_relationship: 'Hapus',
}, },
}, },
empty_state: {
title: 'Tidak ada hubungan',
description: 'Buat hubungan untuk menghubungkan tabel',
},
},
dependencies_section: {
dependencies: 'Dependensi',
filter: 'Saring',
collapse: 'Lipat Semua',
dependency: { dependency: {
dependency: 'Dependensi',
table: 'Tabel', table: 'Tabel',
dependent_table: 'Tampilan Dependen', dependent_table: 'Tampilan Dependen',
delete_dependency: 'Hapus', delete_dependency: 'Hapus',
@@ -206,8 +207,8 @@ export const id_ID: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Tidak ada dependensi', title: 'Tidak ada hubungan',
description: 'Buat tampilan untuk memulai', description: 'Buat hubungan untuk memulai',
}, },
}, },
@@ -247,12 +248,16 @@ export const id_ID: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Tidak ada nilai enum yang ditentukan',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -266,8 +271,14 @@ export const id_ID: LanguageTranslation = {
show_all: 'Tampilkan Semua', show_all: 'Tampilkan Semua',
undo: 'Undo', undo: 'Undo',
redo: 'Redo', redo: 'Redo',
reorder_diagram: 'Atur Ulang Diagram', reorder_diagram: 'Atur Otomatis Diagram',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Sorot Tabel yang Tumpang Tindih', highlight_overlapping_tables: 'Sorot Tabel yang Tumpang Tindih',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -304,7 +315,7 @@ export const id_ID: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Buka Diagram', title: 'Buka Database',
description: 'Pilih diagram untuk dibuka dari daftar di bawah.', description: 'Pilih diagram untuk dibuka dari daftar di bawah.',
table_columns: { table_columns: {
name: 'Name', name: 'Name',
@@ -314,6 +325,12 @@ export const id_ID: LanguageTranslation = {
}, },
cancel: 'Batal', cancel: 'Batal',
open: 'Buka', open: 'Buka',
diagram_actions: {
open: 'Buka',
duplicate: 'Duplikat',
delete: 'Hapus',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -399,6 +416,14 @@ export const id_ID: LanguageTranslation = {
confirm: 'Ubah', confirm: 'Ubah',
}, },
create_table_schema_dialog: {
title: 'Buat Skema Baru',
description:
'Belum ada skema yang tersedia. Buat skema pertama Anda untuk mengatur tabel-tabel Anda.',
create: 'Buat',
cancel: 'Batal',
},
star_us_dialog: { star_us_dialog: {
title: 'Bantu kami meningkatkan!', title: 'Bantu kami meningkatkan!',
description: description:
@@ -455,6 +480,7 @@ export const id_ID: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Tabel Baru', new_table: 'Tabel Baru',
new_view: 'Tampilan Baru',
new_relationship: 'Hubungan Baru', new_relationship: 'Hubungan Baru',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -476,6 +502,9 @@ export const id_ID: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Bahasa', change_language: 'Bahasa',
}, },
on: 'Aktif',
off: 'Nonaktif',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ja: LanguageTranslation = { export const ja: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '新規',
browse: '参照',
tables: 'テーブル',
refs: '参照',
areas: 'エリア',
dependencies: '依存関係',
custom_types: 'カスタムタイプ',
},
menu: { menu: {
file: { actions: {
file: 'ファイル', actions: 'アクション',
new: '新規', new: '新規...',
open: '開く', browse: '参照...',
save: '保存', save: '保存',
import: 'データベースをインポート', import: 'データベースをインポート',
export_sql: 'SQLをエクスポート', export_sql: 'SQLをエクスポート',
export_as: '形式を指定してエクスポート', export_as: '形式を指定してエクスポート',
delete_diagram: 'ダイアグラムを削除', delete_diagram: '削除',
exit: '終了',
}, },
edit: { edit: {
edit: '編集', edit: '編集',
@@ -26,7 +34,10 @@ export const ja: LanguageTranslation = {
hide_sidebar: 'サイドバーを非表示', hide_sidebar: 'サイドバーを非表示',
hide_cardinality: 'カーディナリティを非表示', hide_cardinality: 'カーディナリティを非表示',
show_cardinality: 'カーディナリティを表示', show_cardinality: 'カーディナリティを表示',
hide_field_attributes: 'フィールド属性を非表示',
show_field_attributes: 'フィールド属性を表示',
zoom_on_scroll: 'スクロールでズーム', zoom_on_scroll: 'スクロールでズーム',
show_views: 'データベースビュー',
theme: 'テーマ', theme: 'テーマ',
// TODO: Translate // TODO: Translate
show_dependencies: 'Show Dependencies', show_dependencies: 'Show Dependencies',
@@ -65,22 +76,13 @@ export const ja: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'ダイアグラムを並べ替え', title: 'ダイアグラムを自動配置',
description: description:
'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?', 'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?',
reorder: '並べ替え', reorder: '自動配置',
cancel: 'キャンセル', cancel: 'キャンセル',
}, },
multiple_schemas_alert: {
title: '複数のスキーマ',
description:
'このダイアグラムには{{schemasCount}}個のスキーマがあります。現在表示中: {{formattedSchemas}}。',
dont_show_again: '再表示しない',
change_schema: '変更',
none: 'なし',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'コピー失敗', title: 'コピー失敗',
@@ -117,14 +119,11 @@ export const ja: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'スキーマ:',
filter_by_schema: 'スキーマでフィルタ',
search_schema: 'スキーマを検索...',
no_schemas_found: 'スキーマが見つかりません。',
view_all_options: 'すべてのオプションを表示...', view_all_options: 'すべてのオプションを表示...',
tables_section: { tables_section: {
tables: 'テーブル', tables: 'テーブル',
add_table: 'テーブルを追加', add_table: 'テーブルを追加',
add_view: 'ビューを追加',
filter: 'フィルタ', filter: 'フィルタ',
collapse: 'すべて折りたたむ', collapse: 'すべて折りたたむ',
// TODO: Translate // TODO: Translate
@@ -150,16 +149,23 @@ export const ja: LanguageTranslation = {
field_actions: { field_actions: {
title: 'フィールド属性', title: 'フィールド属性',
unique: 'ユニーク', unique: 'ユニーク',
auto_increment: 'オートインクリメント',
comments: 'コメント', comments: 'コメント',
no_comments: 'コメントがありません', no_comments: 'コメントがありません',
delete_field: 'フィールドを削除', delete_field: 'フィールドを削除',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: '精度',
scale: '小数点以下桁数',
}, },
index_actions: { index_actions: {
title: 'インデックス属性', title: 'インデックス属性',
name: '名前', name: '名前',
unique: 'ユニーク', unique: 'ユニーク',
index_type: 'インデックスタイプ',
delete_index: 'インデックスを削除', delete_index: 'インデックスを削除',
}, },
table_actions: { table_actions: {
@@ -176,12 +182,15 @@ export const ja: LanguageTranslation = {
description: 'テーブルを作成して開始してください', description: 'テーブルを作成して開始してください',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'リレーションシップ', refs: '参照',
filter: 'フィルタ', filter: 'フィルタ',
add_relationship: 'リレーションシップを追加',
collapse: 'すべて折りたたむ', collapse: 'すべて折りたたむ',
add_relationship: 'リレーションシップを追加',
relationships: 'リレーションシップ',
dependencies: '依存関係',
relationship: { relationship: {
relationship: 'リレーションシップ',
primary: '主テーブル', primary: '主テーブル',
foreign: '参照テーブル', foreign: '参照テーブル',
cardinality: 'カーディナリティ', cardinality: 'カーディナリティ',
@@ -191,29 +200,20 @@ export const ja: LanguageTranslation = {
delete_relationship: '削除', delete_relationship: '削除',
}, },
}, },
empty_state: {
title: 'リレーションシップがありません',
description:
'テーブルを接続するためにリレーションシップを作成してください',
},
},
// TODO: Translate
dependencies_section: {
dependencies: 'Dependencies',
filter: 'Filter',
collapse: 'Collapse All',
dependency: { dependency: {
table: 'Table', dependency: '依存関係',
dependent_table: 'Dependent View', table: 'テーブル',
delete_dependency: 'Delete', dependent_table: '依存ビュー',
delete_dependency: '削除',
dependency_actions: { dependency_actions: {
title: 'Actions', title: '操作',
delete_dependency: 'Delete', delete_dependency: '削除',
}, },
}, },
empty_state: { empty_state: {
title: 'No dependencies', title: 'リレーションシップがありません',
description: 'Create a view to get started', description:
'開始するためにリレーションシップを作成してください',
}, },
}, },
@@ -253,12 +253,16 @@ export const ja: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '列挙値が定義されていません',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -272,9 +276,13 @@ export const ja: LanguageTranslation = {
show_all: 'すべて表示', show_all: 'すべて表示',
undo: '元に戻す', undo: '元に戻す',
redo: 'やり直し', redo: 'やり直し',
reorder_diagram: 'ダイアグラムを並べ替え', reorder_diagram: 'ダイアグラムを自動配置',
// TODO: Translate // TODO: Translate
highlight_overlapping_tables: 'Highlight Overlapping Tables', highlight_overlapping_tables: 'Highlight Overlapping Tables',
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear', // TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -312,7 +320,7 @@ export const ja: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'ダイアグラムを開く', title: 'データベースを開く',
description: '以下のリストからダイアグラムを選択してください。', description: '以下のリストからダイアグラムを選択してください。',
table_columns: { table_columns: {
name: '名前', name: '名前',
@@ -322,6 +330,12 @@ export const ja: LanguageTranslation = {
}, },
cancel: 'キャンセル', cancel: 'キャンセル',
open: '開く', open: '開く',
diagram_actions: {
open: '開く',
duplicate: '複製',
delete: '削除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -408,6 +422,14 @@ export const ja: LanguageTranslation = {
confirm: '変更', confirm: '変更',
}, },
create_table_schema_dialog: {
title: '新しいスキーマを作成',
description:
'スキーマがまだ存在しません。テーブルを整理するために最初のスキーマを作成してください。',
create: '作成',
cancel: 'キャンセル',
},
star_us_dialog: { star_us_dialog: {
title: '改善をサポートしてください!', title: '改善をサポートしてください!',
description: description:
@@ -463,6 +485,7 @@ export const ja: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '新しいテーブル', new_table: '新しいテーブル',
new_view: '新しいビュー',
new_relationship: '新しいリレーションシップ', new_relationship: '新しいリレーションシップ',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -485,6 +508,9 @@ export const ja: LanguageTranslation = {
language_select: { language_select: {
change_language: '言語', change_language: '言語',
}, },
on: 'オン',
off: 'オフ',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ko_KR: LanguageTranslation = { export const ko_KR: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '새로 만들기',
browse: '찾아보기',
tables: '테이블',
refs: 'Refs',
areas: '영역',
dependencies: '종속성',
custom_types: '사용자 지정 타입',
},
menu: { menu: {
file: { actions: {
file: '파일', actions: '작업',
new: '새 다이어그램', new: '새로 만들기...',
open: '열기', browse: '찾아보기...',
save: '저장', save: '저장',
import: '데이터베이스 가져오기', import: '데이터베이스 가져오기',
export_sql: 'SQL로 저장', export_sql: 'SQL로 저장',
export_as: '다른 형식으로 저장', export_as: '다른 형식으로 저장',
delete_diagram: '다이어그램 삭제', delete_diagram: '삭제',
exit: '종료',
}, },
edit: { edit: {
edit: '편집', edit: '편집',
@@ -26,7 +34,10 @@ export const ko_KR: LanguageTranslation = {
hide_sidebar: '사이드바 숨기기', hide_sidebar: '사이드바 숨기기',
hide_cardinality: '카디널리티 숨기기', hide_cardinality: '카디널리티 숨기기',
show_cardinality: '카디널리티 보이기', show_cardinality: '카디널리티 보이기',
hide_field_attributes: '필드 속성 숨기기',
show_field_attributes: '필드 속성 보이기',
zoom_on_scroll: '스크롤 시 확대', zoom_on_scroll: '스크롤 시 확대',
show_views: '데이터베이스 뷰',
theme: '테마', theme: '테마',
show_dependencies: '종속성 보이기', show_dependencies: '종속성 보이기',
hide_dependencies: '종속성 숨기기', hide_dependencies: '종속성 숨기기',
@@ -63,22 +74,13 @@ export const ko_KR: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: '다이어그램 정렬', title: '다이어그램 자동 정렬',
description: description:
'이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?', '이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?',
reorder: '정렬', reorder: '자동 정렬',
cancel: '취소', cancel: '취소',
}, },
multiple_schemas_alert: {
title: '다중 스키마',
description:
'현재 다이어그램에 {{schemasCount}}개의 스키마가 있습니다. Currently displaying: {{formattedSchemas}}.',
dont_show_again: '다시 보여주지 마세요',
change_schema: '변경',
none: '없음',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: '복사 실패', title: '복사 실패',
@@ -113,14 +115,11 @@ export const ko_KR: LanguageTranslation = {
copied: '복사됨!', copied: '복사됨!',
side_panel: { side_panel: {
schema: '스키마:',
filter_by_schema: '스키마로 필터링',
search_schema: '스키마 검색...',
no_schemas_found: '스키마를 찾을 수 없습니다.',
view_all_options: '전체 옵션 보기...', view_all_options: '전체 옵션 보기...',
tables_section: { tables_section: {
tables: '테이블', tables: '테이블',
add_table: '테이블 추가', add_table: '테이블 추가',
add_view: '뷰 추가',
filter: '필터', filter: '필터',
collapse: '모두 접기', collapse: '모두 접기',
// TODO: Translate // TODO: Translate
@@ -146,16 +145,23 @@ export const ko_KR: LanguageTranslation = {
field_actions: { field_actions: {
title: '필드 속성', title: '필드 속성',
unique: '유니크 여부', unique: '유니크 여부',
auto_increment: '자동 증가',
comments: '주석', comments: '주석',
no_comments: '주석 없음', no_comments: '주석 없음',
delete_field: '필드 삭제', delete_field: '필드 삭제',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: '정밀도',
scale: '소수점 자릿수',
}, },
index_actions: { index_actions: {
title: '인덱스 속성', title: '인덱스 속성',
name: '인덱스 명', name: '인덱스 명',
unique: '유니크 여부', unique: '유니크 여부',
index_type: '인덱스 타입',
delete_index: '인덱스 삭제', delete_index: '인덱스 삭제',
}, },
table_actions: { table_actions: {
@@ -172,12 +178,15 @@ export const ko_KR: LanguageTranslation = {
description: '테이블을 만들어 시작하세요.', description: '테이블을 만들어 시작하세요.',
}, },
}, },
relationships_section: { refs_section: {
relationships: '연관 관계', refs: 'Refs',
filter: '필터', filter: '필터',
add_relationship: '연관 관계 추가',
collapse: '모두 접기', collapse: '모두 접기',
add_relationship: '연관 관계 추가',
relationships: '연관 관계',
dependencies: '종속성',
relationship: { relationship: {
relationship: '연관 관계',
primary: '주 테이블', primary: '주 테이블',
foreign: '참조 테이블', foreign: '참조 테이블',
cardinality: '카디널리티', cardinality: '카디널리티',
@@ -187,16 +196,8 @@ export const ko_KR: LanguageTranslation = {
delete_relationship: '연관 관계 삭제', delete_relationship: '연관 관계 삭제',
}, },
}, },
empty_state: {
title: '연관 관계',
description: '테이블 연결을 위해 연관 관계를 생성하세요',
},
},
dependencies_section: {
dependencies: '종속성',
filter: '필터',
collapse: '모두 접기',
dependency: { dependency: {
dependency: '종속성',
table: '테이블', table: '테이블',
dependent_table: '뷰 테이블', dependent_table: '뷰 테이블',
delete_dependency: '삭제', delete_dependency: '삭제',
@@ -206,8 +207,8 @@ export const ko_KR: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '뷰 테이블 없음', title: '연관 관계 없음',
description: '뷰 테이블을 만들어 시작하세요.', description: '연관 관계를 만들어 시작하세요.',
}, },
}, },
@@ -247,12 +248,16 @@ export const ko_KR: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '정의된 열거형 값이 없습니다',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -266,8 +271,14 @@ export const ko_KR: LanguageTranslation = {
show_all: '전체 저장', show_all: '전체 저장',
undo: '실행 취소', undo: '실행 취소',
redo: '다시 실행', redo: '다시 실행',
reorder_diagram: '다이어그램 정렬', reorder_diagram: '다이어그램 자동 정렬',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: '겹치는 테이블 강조 표시', highlight_overlapping_tables: '겹치는 테이블 강조 표시',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -304,7 +315,7 @@ export const ko_KR: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: '다이어그램 열기', title: '데이터베이스 열기',
description: '아래의 목록에서 다이어그램을 선택하세요.', description: '아래의 목록에서 다이어그램을 선택하세요.',
table_columns: { table_columns: {
name: '이름', name: '이름',
@@ -314,6 +325,12 @@ export const ko_KR: LanguageTranslation = {
}, },
cancel: '취소', cancel: '취소',
open: '열기', open: '열기',
diagram_actions: {
open: '열기',
duplicate: '복제',
delete: '삭제',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -399,6 +416,14 @@ export const ko_KR: LanguageTranslation = {
confirm: '변경', confirm: '변경',
}, },
create_table_schema_dialog: {
title: '새 스키마 생성',
description:
'아직 스키마가 없습니다. 테이블을 정리하기 위해 첫 번째 스키마를 생성하세요.',
create: '생성',
cancel: '취소',
},
star_us_dialog: { star_us_dialog: {
title: '개선할 수 있도록 도와주세요!', title: '개선할 수 있도록 도와주세요!',
description: description:
@@ -452,6 +477,7 @@ export const ko_KR: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '새 테이블', new_table: '새 테이블',
new_view: '새 뷰',
new_relationship: '새 연관관계', new_relationship: '새 연관관계',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -473,6 +499,9 @@ export const ko_KR: LanguageTranslation = {
language_select: { language_select: {
change_language: '언어', change_language: '언어',
}, },
on: '켜기',
off: '끄기',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const mr: LanguageTranslation = { export const mr: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'नवीन',
browse: 'ब्राउज',
tables: 'टेबल',
refs: 'Refs',
areas: 'क्षेत्रे',
dependencies: 'अवलंबने',
custom_types: 'कस्टम प्रकार',
},
menu: { menu: {
file: { actions: {
file: 'फाइल', actions: 'क्रिया',
new: 'नवीन', new: 'नवीन...',
open: 'उघडा', browse: 'ब्राउज करा...',
save: 'जतन करा', save: 'जतन करा',
import: 'डेटाबेस इम्पोर्ट करा', import: 'डेटाबेस इम्पोर्ट करा',
export_sql: 'SQL एक्स्पोर्ट करा', export_sql: 'SQL एक्स्पोर्ट करा',
export_as: 'म्हणून एक्स्पोर्ट करा', export_as: 'म्हणून एक्स्पोर्ट करा',
delete_diagram: 'आरेख हटवा', delete_diagram: 'हटवा',
exit: 'बाहेर पडा',
}, },
edit: { edit: {
edit: 'संपादन करा', edit: 'संपादन करा',
@@ -26,7 +34,10 @@ export const mr: LanguageTranslation = {
hide_sidebar: 'साइडबार लपवा', hide_sidebar: 'साइडबार लपवा',
hide_cardinality: 'कार्डिनॅलिटी लपवा', hide_cardinality: 'कार्डिनॅलिटी लपवा',
show_cardinality: 'कार्डिनॅलिटी दाखवा', show_cardinality: 'कार्डिनॅलिटी दाखवा',
hide_field_attributes: 'फील्ड गुणधर्म लपवा',
show_field_attributes: 'फील्ड गुणधर्म दाखवा',
zoom_on_scroll: 'स्क्रोलवर झूम करा', zoom_on_scroll: 'स्क्रोलवर झूम करा',
show_views: 'डेटाबेस व्ह्यूज',
theme: 'थीम', theme: 'थीम',
show_dependencies: 'डिपेंडेन्सि दाखवा', show_dependencies: 'डिपेंडेन्सि दाखवा',
hide_dependencies: 'डिपेंडेन्सि लपवा', hide_dependencies: 'डिपेंडेन्सि लपवा',
@@ -64,22 +75,13 @@ export const mr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'आरेख पुनःक्रमित करा', title: 'आरेख स्वयंचलित व्यवस्थित करा',
description: description:
'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?', 'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?',
reorder: 'पुनःक्रमित करा', reorder: 'स्वयंचलित व्यवस्थित करा',
cancel: 'रद्द करा', cancel: 'रद्द करा',
}, },
multiple_schemas_alert: {
title: 'एकाधिक स्कीमा',
description:
'{{schemasCount}} स्कीमा या आरेखात आहेत. सध्या दाखवत आहोत: {{formattedSchemas}}.',
dont_show_again: 'पुन्हा दाखवू नका',
change_schema: 'बदला',
none: 'काहीही नाही',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'कॉपी अयशस्वी', title: 'कॉपी अयशस्वी',
@@ -116,14 +118,11 @@ export const mr: LanguageTranslation = {
copied: 'Copied!', copied: 'Copied!',
side_panel: { side_panel: {
schema: 'स्कीमा:',
filter_by_schema: 'स्कीमा द्वारे फिल्टर करा',
search_schema: 'स्कीमा शोधा...',
no_schemas_found: 'कोणतेही स्कीमा सापडले नाहीत.',
view_all_options: 'सर्व पर्याय पहा...', view_all_options: 'सर्व पर्याय पहा...',
tables_section: { tables_section: {
tables: 'टेबल्स', tables: 'टेबल्स',
add_table: 'टेबल जोडा', add_table: 'टेबल जोडा',
add_view: 'व्ह्यू जोडा',
filter: 'फिल्टर', filter: 'फिल्टर',
collapse: 'सर्व संकुचित करा', collapse: 'सर्व संकुचित करा',
// TODO: Translate // TODO: Translate
@@ -149,16 +148,23 @@ export const mr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'फील्ड गुणधर्म', title: 'फील्ड गुणधर्म',
unique: 'युनिक', unique: 'युनिक',
auto_increment: 'ऑटो इंक्रिमेंट',
comments: 'टिप्पण्या', comments: 'टिप्पण्या',
no_comments: 'कोणत्याही टिप्पणी नाहीत', no_comments: 'कोणत्याही टिप्पणी नाहीत',
delete_field: 'फील्ड हटवा', delete_field: 'फील्ड हटवा',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'अचूकता',
scale: 'प्रमाण',
}, },
index_actions: { index_actions: {
title: 'इंडेक्स गुणधर्म', title: 'इंडेक्स गुणधर्म',
name: 'नाव', name: 'नाव',
unique: 'युनिक', unique: 'युनिक',
index_type: 'इंडेक्स प्रकार',
delete_index: 'इंडेक्स हटवा', delete_index: 'इंडेक्स हटवा',
}, },
table_actions: { table_actions: {
@@ -176,12 +182,15 @@ export const mr: LanguageTranslation = {
description: 'सुरू करण्यासाठी एक टेबल तयार करा', description: 'सुरू करण्यासाठी एक टेबल तयार करा',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'रिलेशनशिप', refs: 'Refs',
filter: 'फिल्टर', filter: 'फिल्टर',
add_relationship: 'रिलेशनशिप जोडा',
collapse: 'सर्व संकुचित करा', collapse: 'सर्व संकुचित करा',
add_relationship: 'रिलेशनशिप जोडा',
relationships: 'रिलेशनशिप',
dependencies: 'डिपेंडेन्सि',
relationship: { relationship: {
relationship: 'रिलेशनशिप',
primary: 'प्राथमिक टेबल', primary: 'प्राथमिक टेबल',
foreign: 'रेफरंस टेबल', foreign: 'रेफरंस टेबल',
cardinality: 'कार्डिनॅलिटी', cardinality: 'कार्डिनॅलिटी',
@@ -191,17 +200,8 @@ export const mr: LanguageTranslation = {
delete_relationship: 'हटवा', delete_relationship: 'हटवा',
}, },
}, },
empty_state: {
title: 'कोणतेही रिलेशनशिप नाहीत',
description:
'टेबल्स कनेक्ट करण्यासाठी एक रिलेशनशिप तयार करा',
},
},
dependencies_section: {
dependencies: 'डिपेंडेन्सि',
filter: 'फिल्टर',
collapse: 'सर्व संकुचित करा',
dependency: { dependency: {
dependency: 'डिपेंडेन्सि',
table: 'टेबल', table: 'टेबल',
dependent_table: 'डिपेंडेन्सि दृश्य', dependent_table: 'डिपेंडेन्सि दृश्य',
delete_dependency: 'हटवा', delete_dependency: 'हटवा',
@@ -211,8 +211,8 @@ export const mr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'कोणत्याही डिपेंडेन्सि नाहीत', title: 'कोणतेही रिलेशनशिप नाहीत',
description: 'सुरू करण्यासाठी एक दृश्य तयार करा', description: 'सुरू करण्यासाठी एक रिलेशनशिप तयार करा',
}, },
}, },
@@ -252,12 +252,16 @@ export const mr: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'कोणतीही enum मूल्ये परिभाषित नाहीत',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -271,8 +275,14 @@ export const mr: LanguageTranslation = {
show_all: 'सर्व दाखवा', show_all: 'सर्व दाखवा',
undo: 'पूर्ववत करा', undo: 'पूर्ववत करा',
redo: 'पुन्हा करा', redo: 'पुन्हा करा',
reorder_diagram: 'आरेख पुनःक्रमित करा', reorder_diagram: 'आरेख स्वयंचलित व्यवस्थित करा',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'ओव्हरलॅपिंग टेबल्स हायलाइट करा', highlight_overlapping_tables: 'ओव्हरलॅपिंग टेबल्स हायलाइट करा',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -311,7 +321,7 @@ export const mr: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'आरेख उघडा', title: 'डेटाबेस उघडा',
description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.', description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.',
table_columns: { table_columns: {
name: 'नाव', name: 'नाव',
@@ -321,6 +331,12 @@ export const mr: LanguageTranslation = {
}, },
cancel: 'रद्द करा', cancel: 'रद्द करा',
open: 'उघडा', open: 'उघडा',
diagram_actions: {
open: 'उघडा',
duplicate: 'डुप्लिकेट',
delete: 'हटवा',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -407,6 +423,14 @@ export const mr: LanguageTranslation = {
confirm: 'बदला', confirm: 'बदला',
}, },
create_table_schema_dialog: {
title: 'नवीन स्कीमा तयार करा',
description:
'अजून कोणतीही स्कीमा अस्तित्वात नाही. आपल्या टेबल्स व्यवस्थित करण्यासाठी आपली पहिली स्कीमा तयार करा.',
create: 'तयार करा',
cancel: 'रद्द करा',
},
star_us_dialog: { star_us_dialog: {
title: 'आम्हाला सुधारण्यास मदत करा!', title: 'आम्हाला सुधारण्यास मदत करा!',
description: description:
@@ -465,6 +489,7 @@ export const mr: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'नवीन टेबल', new_table: 'नवीन टेबल',
new_view: 'नवीन व्ह्यू',
new_relationship: 'नवीन रिलेशनशिप', new_relationship: 'नवीन रिलेशनशिप',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -488,6 +513,9 @@ export const mr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा बदला', change_language: 'भाषा बदला',
}, },
on: 'चालू',
off: 'बंद',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ne: LanguageTranslation = { export const ne: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'नयाँ',
browse: 'ब्राउज',
tables: 'टेबलहरू',
refs: 'Refs',
areas: 'क्षेत्रहरू',
dependencies: 'निर्भरताहरू',
custom_types: 'कस्टम प्रकारहरू',
},
menu: { menu: {
file: { actions: {
file: 'फाइल', actions: 'कार्यहरू',
new: 'नयाँ', new: 'नयाँ...',
open: 'खोल्नुहोस्', browse: 'ब्राउज गर्नुहोस्...',
save: 'सुरक्षित गर्नुहोस्', save: 'सुरक्षित गर्नुहोस्',
import: 'डाटाबेस आयात गर्नुहोस्', import: 'डाटाबेस आयात गर्नुहोस्',
export_sql: 'SQL निर्यात गर्नुहोस्', export_sql: 'SQL निर्यात गर्नुहोस्',
export_as: 'निर्यात गर्नुहोस्', export_as: 'निर्यात गर्नुहोस्',
delete_diagram: 'डायाग्राम हटाउनुहोस्', delete_diagram: 'हटाउनुहोस्',
exit: 'बाहिर निस्कनुहोस्',
}, },
edit: { edit: {
edit: 'सम्पादन', edit: 'सम्पादन',
@@ -26,7 +34,10 @@ export const ne: LanguageTranslation = {
hide_sidebar: 'साइडबार लुकाउनुहोस्', hide_sidebar: 'साइडबार लुकाउनुहोस्',
hide_cardinality: 'कार्डिन्यालिटी लुकाउनुहोस्', hide_cardinality: 'कार्डिन्यालिटी लुकाउनुहोस्',
show_cardinality: 'कार्डिन्यालिटी देखाउनुहोस्', show_cardinality: 'कार्डिन्यालिटी देखाउनुहोस्',
hide_field_attributes: 'फिल्ड विशेषताहरू लुकाउनुहोस्',
show_field_attributes: 'फिल्ड विशेषताहरू देखाउनुहोस्',
zoom_on_scroll: 'स्क्रोलमा जुम गर्नुहोस्', zoom_on_scroll: 'स्क्रोलमा जुम गर्नुहोस्',
show_views: 'डाटाबेस भ्यूहरू',
theme: 'थिम', theme: 'थिम',
show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्', show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्',
hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्', hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्',
@@ -64,22 +75,13 @@ export const ne: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'डायाग्राम पुनः क्रमबद्ध गर्नुहोस्', title: 'डायाग्राम स्वचालित मिलाउनुहोस्',
description: description:
'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।', 'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।',
reorder: 'पुनः क्रमबद्ध गर्नुहोस्', reorder: 'स्वचालित मिलाउनुहोस्',
cancel: 'रद्द गर्नुहोस्', cancel: 'रद्द गर्नुहोस्',
}, },
multiple_schemas_alert: {
title: 'विविध स्कीमहरू',
description:
'{{schemasCount}} डायाग्राममा स्कीमहरू। हालको रूपमा देखाइएको छ: {{formattedSchemas}}।',
dont_show_again: 'फेरि देखाउन नदिनुहोस्',
change_schema: 'स्कीम परिवर्तन गर्नुहोस्',
none: 'कुनै पनि छैन',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'प्रतिलिपि असफल', title: 'प्रतिलिपि असफल',
@@ -114,14 +116,11 @@ export const ne: LanguageTranslation = {
copied: 'प्रतिलिपि गरियो!', copied: 'प्रतिलिपि गरियो!',
side_panel: { side_panel: {
schema: 'स्कीम:',
filter_by_schema: 'स्कीम अनुसार फिल्टर गर्नुहोस्',
search_schema: 'स्कीम खोज्नुहोस्...',
no_schemas_found: 'कुनै स्कीमहरू फेला परेनन्',
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्', view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
tables_section: { tables_section: {
tables: 'तालिकाहरू', tables: 'तालिकाहरू',
add_table: 'तालिका थप्नुहोस्', add_table: 'तालिका थप्नुहोस्',
add_view: 'भ्यू थप्नुहोस्',
filter: 'फिल्टर', filter: 'फिल्टर',
collapse: 'सबै लुकाउनुहोस्', collapse: 'सबै लुकाउनुहोस्',
// TODO: Translate // TODO: Translate
@@ -147,16 +146,23 @@ export const ne: LanguageTranslation = {
field_actions: { field_actions: {
title: 'क्षेत्र विशेषताहरू', title: 'क्षेत्र विशेषताहरू',
unique: 'अनन्य', unique: 'अनन्य',
auto_increment: 'स्वचालित वृद्धि',
comments: 'टिप्पणीहरू', comments: 'टिप्पणीहरू',
no_comments: 'कुनै टिप्पणीहरू छैनन्', no_comments: 'कुनै टिप्पणीहरू छैनन्',
delete_field: 'क्षेत्र हटाउनुहोस्', delete_field: 'क्षेत्र हटाउनुहोस्',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'परिशुद्धता',
scale: 'स्केल',
}, },
index_actions: { index_actions: {
title: 'सूचक विशेषताहरू', title: 'सूचक विशेषताहरू',
name: 'नाम', name: 'नाम',
unique: 'अनन्य', unique: 'अनन्य',
index_type: 'इन्डेक्स प्रकार',
delete_index: 'सूचक हटाउनुहोस्', delete_index: 'सूचक हटाउनुहोस्',
}, },
table_actions: { table_actions: {
@@ -173,12 +179,15 @@ export const ne: LanguageTranslation = {
description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्', description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'सम्बन्धहरू', refs: 'Refs',
filter: 'फिल्टर', filter: 'फिल्टर',
add_relationship: 'सम्बन्ध थप्नुहोस्',
collapse: 'सबै लुकाउनुहोस्', collapse: 'सबै लुकाउनुहोस्',
add_relationship: 'सम्बन्ध थप्नुहोस्',
relationships: 'सम्बन्धहरू',
dependencies: 'डिपेन्डेन्सीहरू',
relationship: { relationship: {
relationship: 'सम्बन्ध',
primary: 'मुख्य तालिका', primary: 'मुख्य तालिका',
foreign: 'परिचित तालिका', foreign: 'परिचित तालिका',
cardinality: 'कार्डिन्यालिटी', cardinality: 'कार्डिन्यालिटी',
@@ -188,16 +197,8 @@ export const ne: LanguageTranslation = {
delete_relationship: 'हटाउनुहोस्', delete_relationship: 'हटाउनुहोस्',
}, },
}, },
empty_state: {
title: 'कुनै सम्बन्धहरू छैनन्',
description: 'तालिकाहरू जोड्नका लागि एक सम्बन्ध बनाउनुहोस्',
},
},
dependencies_section: {
dependencies: 'डिपेन्डेन्सीहरू',
filter: 'फिल्टर',
collapse: 'सबै लुकाउनुहोस्',
dependency: { dependency: {
dependency: 'डिपेन्डेन्सी',
table: 'तालिका', table: 'तालिका',
dependent_table: 'विचलित तालिका', dependent_table: 'विचलित तालिका',
delete_dependency: 'हटाउनुहोस्', delete_dependency: 'हटाउनुहोस्',
@@ -207,9 +208,8 @@ export const ne: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'कुनै डिपेन्डेन्सीहरू छैनन्', title: 'कुनै सम्बन्धहरू छैनन्',
description: description: 'सुरु गर्नका लागि एक सम्बन्ध बनाउनुहोस्',
'डिपेन्डेन्सीहरू देखाउनका लागि एक व्यू बनाउनुहोस्',
}, },
}, },
@@ -249,12 +249,16 @@ export const ne: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'कुनै enum मानहरू परिभाषित छैनन्',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -268,9 +272,15 @@ export const ne: LanguageTranslation = {
show_all: 'सबै देखाउनुहोस्', show_all: 'सबै देखाउनुहोस्',
undo: 'पूर्ववत', undo: 'पूर्ववत',
redo: 'पुनः गर्नुहोस्', redo: 'पुनः गर्नुहोस्',
reorder_diagram: 'पुनः क्रमबद्ध गर्नुहोस्', reorder_diagram: 'डायाग्राम स्वचालित मिलाउनुहोस्',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: highlight_overlapping_tables:
'अतिरिक्त तालिकाहरू हाइलाइट गर्नुहोस्', 'अतिरिक्त तालिकाहरू हाइलाइट गर्नुहोस्',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -307,7 +317,7 @@ export const ne: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'डायाग्राम खोल्नुहोस्', title: 'डाटाबेस खोल्नुहोस्',
description: description:
'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।', 'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।',
table_columns: { table_columns: {
@@ -318,6 +328,12 @@ export const ne: LanguageTranslation = {
}, },
cancel: 'रद्द गर्नुहोस्', cancel: 'रद्द गर्नुहोस्',
open: 'खोल्नुहोस्', open: 'खोल्नुहोस्',
diagram_actions: {
open: 'खोल्नुहोस्',
duplicate: 'डुप्लिकेट',
delete: 'मेटाउनुहोस्',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -404,6 +420,14 @@ export const ne: LanguageTranslation = {
confirm: 'परिवर्तन गर्नुहोस्', confirm: 'परिवर्तन गर्नुहोस्',
}, },
create_table_schema_dialog: {
title: 'नयाँ स्कीम सिर्जना गर्नुहोस्',
description:
'अहिलेसम्म कुनै स्कीम अस्तित्वमा छैन। आफ्ना तालिकाहरू व्यवस्थित गर्न आफ्नो पहिलो स्कीम सिर्जना गर्नुहोस्।',
create: 'सिर्जना गर्नुहोस्',
cancel: 'रद्द गर्नुहोस्',
},
star_us_dialog: { star_us_dialog: {
title: 'हामीलाई अझ राम्रो हुन मदत गर्नुहोस!', title: 'हामीलाई अझ राम्रो हुन मदत गर्नुहोस!',
description: description:
@@ -459,6 +483,7 @@ export const ne: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'नयाँ तालिका', new_table: 'नयाँ तालिका',
new_view: 'नयाँ भ्यू',
new_relationship: 'नयाँ सम्बन्ध', new_relationship: 'नयाँ सम्बन्ध',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -480,6 +505,9 @@ export const ne: LanguageTranslation = {
language_select: { language_select: {
change_language: 'भाषा परिवर्तन गर्नुहोस्', change_language: 'भाषा परिवर्तन गर्नुहोस्',
}, },
on: 'सक्रिय',
off: 'निष्क्रिय',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const pt_BR: LanguageTranslation = { export const pt_BR: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Novo',
browse: 'Navegar',
tables: 'Tabelas',
refs: 'Refs',
areas: 'Áreas',
dependencies: 'Dependências',
custom_types: 'Tipos Personalizados',
},
menu: { menu: {
file: { actions: {
file: 'Arquivo', actions: 'Ações',
new: 'Novo', new: 'Novo...',
open: 'Abrir', browse: 'Navegar...',
save: 'Salvar', save: 'Salvar',
import: 'Importar Banco de Dados', import: 'Importar Banco de Dados',
export_sql: 'Exportar SQL', export_sql: 'Exportar SQL',
export_as: 'Exportar como', export_as: 'Exportar como',
delete_diagram: 'Excluir Diagrama', delete_diagram: 'Excluir',
exit: 'Sair',
}, },
edit: { edit: {
edit: 'Editar', edit: 'Editar',
@@ -26,7 +34,10 @@ export const pt_BR: LanguageTranslation = {
hide_sidebar: 'Ocultar Barra Lateral', hide_sidebar: 'Ocultar Barra Lateral',
hide_cardinality: 'Ocultar Cardinalidade', hide_cardinality: 'Ocultar Cardinalidade',
show_cardinality: 'Mostrar Cardinalidade', show_cardinality: 'Mostrar Cardinalidade',
hide_field_attributes: 'Ocultar Atributos de Campo',
show_field_attributes: 'Mostrar Atributos de Campo',
zoom_on_scroll: 'Zoom ao Rolar', zoom_on_scroll: 'Zoom ao Rolar',
show_views: 'Visualizações do Banco de Dados',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Mostrar Dependências', show_dependencies: 'Mostrar Dependências',
hide_dependencies: 'Ocultar Dependências', hide_dependencies: 'Ocultar Dependências',
@@ -64,22 +75,13 @@ export const pt_BR: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Reordenar Diagrama', title: 'Organizar Diagrama Automaticamente',
description: description:
'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?', 'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?',
reorder: 'Reordenar', reorder: 'Organizar Automaticamente',
cancel: 'Cancelar', cancel: 'Cancelar',
}, },
multiple_schemas_alert: {
title: 'Múltiplos Esquemas',
description:
'{{schemasCount}} esquemas neste diagrama. Atualmente exibindo: {{formattedSchemas}}.',
dont_show_again: 'Não mostrar novamente',
change_schema: 'Alterar',
none: 'nenhum',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Falha na cópia', title: 'Falha na cópia',
@@ -114,14 +116,11 @@ export const pt_BR: LanguageTranslation = {
copied: 'Copiado!', copied: 'Copiado!',
side_panel: { side_panel: {
schema: 'Esquema:',
filter_by_schema: 'Filtrar por esquema',
search_schema: 'Buscar esquema...',
no_schemas_found: 'Nenhum esquema encontrado.',
view_all_options: 'Ver todas as Opções...', view_all_options: 'Ver todas as Opções...',
tables_section: { tables_section: {
tables: 'Tabelas', tables: 'Tabelas',
add_table: 'Adicionar Tabela', add_table: 'Adicionar Tabela',
add_view: 'Adicionar Visualização',
filter: 'Filtrar', filter: 'Filtrar',
collapse: 'Colapsar Todas', collapse: 'Colapsar Todas',
// TODO: Translate // TODO: Translate
@@ -147,16 +146,23 @@ export const pt_BR: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Atributos do Campo', title: 'Atributos do Campo',
unique: 'Único', unique: 'Único',
auto_increment: 'Incremento Automático',
comments: 'Comentários', comments: 'Comentários',
no_comments: 'Sem comentários', no_comments: 'Sem comentários',
delete_field: 'Excluir Campo', delete_field: 'Excluir Campo',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Precisão',
scale: 'Escala',
}, },
index_actions: { index_actions: {
title: 'Atributos do Índice', title: 'Atributos do Índice',
name: 'Nome', name: 'Nome',
unique: 'Único', unique: 'Único',
index_type: 'Tipo de Índice',
delete_index: 'Excluir Índice', delete_index: 'Excluir Índice',
}, },
table_actions: { table_actions: {
@@ -173,12 +179,15 @@ export const pt_BR: LanguageTranslation = {
description: 'Crie uma tabela para começar', description: 'Crie uma tabela para começar',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Relacionamentos', refs: 'Refs',
filter: 'Filtrar', filter: 'Filtrar',
add_relationship: 'Adicionar Relacionamento',
collapse: 'Colapsar Todas', collapse: 'Colapsar Todas',
add_relationship: 'Adicionar Relacionamento',
relationships: 'Relacionamentos',
dependencies: 'Dependências',
relationship: { relationship: {
relationship: 'Relacionamento',
primary: 'Tabela Primária', primary: 'Tabela Primária',
foreign: 'Tabela Referenciada', foreign: 'Tabela Referenciada',
cardinality: 'Cardinalidade', cardinality: 'Cardinalidade',
@@ -188,16 +197,8 @@ export const pt_BR: LanguageTranslation = {
delete_relationship: 'Excluir', delete_relationship: 'Excluir',
}, },
}, },
empty_state: {
title: 'Sem relacionamentos',
description: 'Crie um relacionamento para conectar tabelas',
},
},
dependencies_section: {
dependencies: 'Dependências',
filter: 'Filtrar',
collapse: 'Colapsar Todas',
dependency: { dependency: {
dependency: 'Dependência',
table: 'Tabela', table: 'Tabela',
dependent_table: 'Visualização Dependente', dependent_table: 'Visualização Dependente',
delete_dependency: 'Excluir', delete_dependency: 'Excluir',
@@ -207,8 +208,8 @@ export const pt_BR: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Sem dependências', title: 'Sem relacionamentos',
description: 'Crie uma visualização para começar', description: 'Crie um relacionamento para começar',
}, },
}, },
@@ -248,12 +249,16 @@ export const pt_BR: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Nenhum valor de enum definido',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -267,8 +272,14 @@ export const pt_BR: LanguageTranslation = {
show_all: 'Mostrar Tudo', show_all: 'Mostrar Tudo',
undo: 'Desfazer', undo: 'Desfazer',
redo: 'Refazer', redo: 'Refazer',
reorder_diagram: 'Reordenar Diagrama', reorder_diagram: 'Organizar Diagrama Automaticamente',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Destacar Tabelas Sobrepostas', highlight_overlapping_tables: 'Destacar Tabelas Sobrepostas',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -306,7 +317,7 @@ export const pt_BR: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Abrir Diagrama', title: 'Abrir Banco de Dados',
description: 'Selecione um diagrama para abrir da lista abaixo.', description: 'Selecione um diagrama para abrir da lista abaixo.',
table_columns: { table_columns: {
name: 'Nome', name: 'Nome',
@@ -316,6 +327,12 @@ export const pt_BR: LanguageTranslation = {
}, },
cancel: 'Cancelar', cancel: 'Cancelar',
open: 'Abrir', open: 'Abrir',
diagram_actions: {
open: 'Abrir',
duplicate: 'Duplicar',
delete: 'Excluir',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -402,6 +419,14 @@ export const pt_BR: LanguageTranslation = {
confirm: 'Alterar', confirm: 'Alterar',
}, },
create_table_schema_dialog: {
title: 'Criar Novo Esquema',
description:
'Ainda não existem esquemas. Crie seu primeiro esquema para organizar suas tabelas.',
create: 'Criar',
cancel: 'Cancelar',
},
star_us_dialog: { star_us_dialog: {
title: 'Ajude-nos a melhorar!', title: 'Ajude-nos a melhorar!',
description: description:
@@ -457,6 +482,7 @@ export const pt_BR: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Nova Tabela', new_table: 'Nova Tabela',
new_view: 'Nova Visualização',
new_relationship: 'Novo Relacionamento', new_relationship: 'Novo Relacionamento',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -479,6 +505,9 @@ export const pt_BR: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Idioma', change_language: 'Idioma',
}, },
on: 'Ligado',
off: 'Desligado',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const ru: LanguageTranslation = { export const ru: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Новая',
browse: 'Обзор',
tables: 'Таблицы',
refs: 'Ссылки',
areas: 'Области',
dependencies: 'Зависимости',
custom_types: 'Пользовательские типы',
},
menu: { menu: {
file: { actions: {
file: 'Файл', actions: 'Действия',
new: 'Создать', new: 'Новая...',
open: 'Открыть', browse: 'Обзор...',
save: 'Сохранить', save: 'Сохранить',
import: 'Импортировать базу данных', import: 'Импортировать базу данных',
export_sql: 'Экспорт SQL', export_sql: 'Экспорт SQL',
export_as: 'Экспортировать как', export_as: 'Экспортировать как',
delete_diagram: 'Удалить диаграмму', delete_diagram: 'Удалить',
exit: 'Выход',
}, },
edit: { edit: {
edit: 'Изменение', edit: 'Изменение',
@@ -26,7 +34,10 @@ export const ru: LanguageTranslation = {
hide_sidebar: 'Скрыть боковую панель', hide_sidebar: 'Скрыть боковую панель',
hide_cardinality: 'Скрыть виды связи', hide_cardinality: 'Скрыть виды связи',
show_cardinality: 'Показать виды связи', show_cardinality: 'Показать виды связи',
show_field_attributes: 'Показать атрибуты поля',
hide_field_attributes: 'Скрыть атрибуты поля',
zoom_on_scroll: 'Увеличение при прокрутке', zoom_on_scroll: 'Увеличение при прокрутке',
show_views: 'Представления базы данных',
theme: 'Тема', theme: 'Тема',
show_dependencies: 'Показать зависимости', show_dependencies: 'Показать зависимости',
hide_dependencies: 'Скрыть зависимости', hide_dependencies: 'Скрыть зависимости',
@@ -62,22 +73,13 @@ export const ru: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Переупорядочить диаграмму', title: 'Автоматическая расстановка диаграммы',
description: description:
'Это действие переставит все таблицы на диаграмме. Хотите продолжить?', 'Это действие переставит все таблицы на диаграмме. Хотите продолжить?',
reorder: 'Изменить порядок', reorder: 'Автоматическая расстановка',
cancel: 'Отменить', cancel: 'Отменить',
}, },
multiple_schemas_alert: {
title: 'Множественные схемы',
description:
'{{schemasCount}} схем в этой диаграмме. В данный момент отображается: {{formattedSchemas}}.',
dont_show_again: 'Больше не показывать',
change_schema: 'Изменить',
none: 'никто',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Ошибка копирования', title: 'Ошибка копирования',
@@ -111,14 +113,11 @@ export const ru: LanguageTranslation = {
show_less: 'Показать меньше', show_less: 'Показать меньше',
side_panel: { side_panel: {
schema: 'Схема:',
filter_by_schema: 'Фильтр по схеме',
search_schema: 'Схема поиска...',
no_schemas_found: 'Схемы не найдены.',
view_all_options: 'Просмотреть все варианты...', view_all_options: 'Просмотреть все варианты...',
tables_section: { tables_section: {
tables: 'Таблицы', tables: 'Таблицы',
add_table: 'Добавить таблицу', add_table: 'Добавить таблицу',
add_view: 'Добавить представление',
filter: 'Фильтр', filter: 'Фильтр',
collapse: 'Свернуть все', collapse: 'Свернуть все',
clear: 'Очистить фильтр', clear: 'Очистить фильтр',
@@ -144,15 +143,22 @@ export const ru: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Атрибуты поля', title: 'Атрибуты поля',
unique: 'Уникальный', unique: 'Уникальный',
auto_increment: 'Автоинкремент',
comments: 'Комментарии', comments: 'Комментарии',
no_comments: 'Нет комментария', no_comments: 'Нет комментария',
delete_field: 'Удалить поле', delete_field: 'Удалить поле',
// TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
character_length: 'Макс. длина', character_length: 'Макс. длина',
precision: 'Точность',
scale: 'Масштаб',
}, },
index_actions: { index_actions: {
title: 'Атрибуты индекса', title: 'Атрибуты индекса',
name: 'Имя', name: 'Имя',
unique: 'Уникальный', unique: 'Уникальный',
index_type: 'Тип индекса',
delete_index: 'Удалить индекс', delete_index: 'Удалить индекс',
}, },
table_actions: { table_actions: {
@@ -169,12 +175,15 @@ export const ru: LanguageTranslation = {
description: 'Создайте таблицу, чтобы начать', description: 'Создайте таблицу, чтобы начать',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Отношения', refs: 'Ссылки',
filter: 'Фильтр', filter: 'Фильтр',
add_relationship: 'Добавить отношение',
collapse: 'Свернуть все', collapse: 'Свернуть все',
add_relationship: 'Добавить отношение',
relationships: 'Отношения',
dependencies: 'Зависимости',
relationship: { relationship: {
relationship: 'Отношение',
primary: 'Основная таблица', primary: 'Основная таблица',
foreign: 'Справочная таблица', foreign: 'Справочная таблица',
cardinality: 'Тип множественной связи', cardinality: 'Тип множественной связи',
@@ -184,18 +193,10 @@ export const ru: LanguageTranslation = {
delete_relationship: 'Удалить', delete_relationship: 'Удалить',
}, },
}, },
empty_state: {
title: 'Нет отношений',
description: 'Создайте связь для соединения таблиц',
},
},
dependencies_section: {
dependencies: 'Зависимости',
filter: 'Фильтр',
collapse: 'Свернуть все',
dependency: { dependency: {
table: 'Стол', dependency: 'Зависимость',
dependent_table: 'Зависимый вид', table: 'Таблица',
dependent_table: 'Зависимое представление',
delete_dependency: 'Удалить', delete_dependency: 'Удалить',
dependency_actions: { dependency_actions: {
title: 'Действия', title: 'Действия',
@@ -203,8 +204,8 @@ export const ru: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Нет зависимостей', title: 'Нет отношений',
description: 'Создайте представление, чтобы начать', description: 'Создайте отношение, чтобы начать',
}, },
}, },
@@ -245,12 +246,16 @@ export const ru: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Значения перечисления не определены',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -264,8 +269,14 @@ export const ru: LanguageTranslation = {
show_all: 'Показать все', show_all: 'Показать все',
undo: 'Отменить', undo: 'Отменить',
redo: 'Вернуть', redo: 'Вернуть',
reorder_diagram: 'Переупорядочить диаграмму', reorder_diagram: 'Автоматическая расстановка диаграммы',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Выделение перекрывающихся таблиц', highlight_overlapping_tables: 'Выделение перекрывающихся таблиц',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -302,7 +313,7 @@ export const ru: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Открыть диаграмму', title: 'Открыть базу данных',
description: description:
'Выберите диаграмму, которую нужно открыть, из списка ниже.', 'Выберите диаграмму, которую нужно открыть, из списка ниже.',
table_columns: { table_columns: {
@@ -313,6 +324,12 @@ export const ru: LanguageTranslation = {
}, },
cancel: 'Отмена', cancel: 'Отмена',
open: 'Открыть', open: 'Открыть',
diagram_actions: {
open: 'Открыть',
duplicate: 'Дублировать',
delete: 'Удалить',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -399,6 +416,14 @@ export const ru: LanguageTranslation = {
confirm: 'Изменить', confirm: 'Изменить',
}, },
create_table_schema_dialog: {
title: 'Создать новую схему',
description:
'Схемы еще не существуют. Создайте вашу первую схему, чтобы организовать таблицы.',
create: 'Создать',
cancel: 'Отменить',
},
star_us_dialog: { star_us_dialog: {
title: 'Помогите нам стать лучше!', title: 'Помогите нам стать лучше!',
description: description:
@@ -453,6 +478,7 @@ export const ru: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Создать таблицу', new_table: 'Создать таблицу',
new_view: 'Новое представление',
new_relationship: 'Создать отношение', new_relationship: 'Создать отношение',
new_area: 'Новая область', new_area: 'Новая область',
}, },
@@ -474,6 +500,9 @@ export const ru: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Сменить язык', change_language: 'Сменить язык',
}, },
on: 'Вкл',
off: 'Выкл',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const te: LanguageTranslation = { export const te: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'కొత్తది',
browse: 'బ్రాఉజ్',
tables: 'టేబల్లు',
refs: 'సంబంధాలు',
areas: 'ప్రదేశాలు',
dependencies: 'ఆధారతలు',
custom_types: 'కస్టమ్ టైప్స్',
},
menu: { menu: {
file: { actions: {
file: 'ఫైల్', actions: 'చర్యలు',
new: 'కొత్తది', new: 'కొత్తది...',
open: 'తెరవు', browse: 'బ్రాఉజ్ చేయండి...',
save: 'సేవ్', save: 'సేవ్',
import: 'డేటాబేస్‌ను దిగుమతి చేసుకోండి', import: 'డేటాబేస్‌ను దిగుమతి చేసుకోండి',
export_sql: 'SQL ఎగుమతి', export_sql: 'SQL ఎగుమతి',
export_as: 'వగా ఎగుమతి చేయండి', export_as: 'వగా ఎగుమతి చేయండి',
delete_diagram: 'చిత్రాన్ని తొలగించండి', delete_diagram: 'తొలగించండి',
exit: 'నిష్క్రమించు',
}, },
edit: { edit: {
edit: 'సవరించు', edit: 'సవరించు',
@@ -26,7 +34,10 @@ export const te: LanguageTranslation = {
hide_sidebar: 'సైడ్‌బార్ దాచండి', hide_sidebar: 'సైడ్‌బార్ దాచండి',
hide_cardinality: 'కార్డినాలిటీని దాచండి', hide_cardinality: 'కార్డినాలిటీని దాచండి',
show_cardinality: 'కార్డినాలిటీని చూపించండి', show_cardinality: 'కార్డినాలిటీని చూపించండి',
show_field_attributes: 'ఫీల్డ్ గుణాలను చూపించు',
hide_field_attributes: 'ఫీల్డ్ గుణాలను దాచండి',
zoom_on_scroll: 'స్క్రోల్‌పై జూమ్', zoom_on_scroll: 'స్క్రోల్‌పై జూమ్',
show_views: 'డేటాబేస్ వ్యూలు',
theme: 'థీమ్', theme: 'థీమ్',
show_dependencies: 'ఆధారాలు చూపించండి', show_dependencies: 'ఆధారాలు చూపించండి',
hide_dependencies: 'ఆధారాలను దాచండి', hide_dependencies: 'ఆధారాలను దాచండి',
@@ -64,22 +75,13 @@ export const te: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'చిత్రాన్ని పునఃసరిచేయండి', title: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
description: description:
'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?', 'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?',
reorder: 'పునఃసరిచేయండి', reorder: 'స్వయంచాలకంగా అమర్చండి',
cancel: 'రద్దు', cancel: 'రద్దు',
}, },
multiple_schemas_alert: {
title: 'బహుళ స్కీమాలు',
description:
'{{schemasCount}} స్కీమాలు ఈ చిత్రంలో ఉన్నాయి. ప్రస్తుత స్కీమాలు: {{formattedSchemas}}.',
dont_show_again: 'మరలా చూపించవద్దు',
change_schema: 'మార్చు',
none: 'ఎదరికాదు',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'కాపీ విఫలమైంది', title: 'కాపీ విఫలమైంది',
@@ -114,14 +116,11 @@ export const te: LanguageTranslation = {
copied: 'కాపీ చేయబడింది!', copied: 'కాపీ చేయబడింది!',
side_panel: { side_panel: {
schema: 'స్కీమా:',
filter_by_schema: 'స్కీమా ద్వారా ఫిల్టర్ చేయండి',
search_schema: 'స్కీమా కోసం శోధించండి...',
no_schemas_found: 'ఏ స్కీమాలు కూడా కనుగొనబడలేదు.',
view_all_options: 'అన్ని ఎంపికలను చూడండి...', view_all_options: 'అన్ని ఎంపికలను చూడండి...',
tables_section: { tables_section: {
tables: 'పట్టికలు', tables: 'పట్టికలు',
add_table: 'పట్టికను జోడించు', add_table: 'పట్టికను జోడించు',
add_view: 'వ్యూ జోడించండి',
filter: 'ఫిల్టర్', filter: 'ఫిల్టర్',
collapse: 'అన్ని కూల్ చేయి', collapse: 'అన్ని కూల్ చేయి',
// TODO: Translate // TODO: Translate
@@ -147,16 +146,23 @@ export const te: LanguageTranslation = {
field_actions: { field_actions: {
title: 'ఫీల్డ్ గుణాలు', title: 'ఫీల్డ్ గుణాలు',
unique: 'అద్వితీయ', unique: 'అద్వితీయ',
auto_increment: 'ఆటో ఇంక్రిమెంట్',
comments: 'వ్యాఖ్యలు', comments: 'వ్యాఖ్యలు',
no_comments: 'వ్యాఖ్యలు లేవు', no_comments: 'వ్యాఖ్యలు లేవు',
delete_field: 'ఫీల్డ్ తొలగించు', delete_field: 'ఫీల్డ్ తొలగించు',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'సూక్ష్మత',
scale: 'స్కేల్',
}, },
index_actions: { index_actions: {
title: 'ఇండెక్స్ గుణాలు', title: 'ఇండెక్స్ గుణాలు',
name: 'పేరు', name: 'పేరు',
unique: 'అద్వితీయ', unique: 'అద్వితీయ',
index_type: 'ఇండెక్స్ రకం',
delete_index: 'ఇండెక్స్ తొలగించు', delete_index: 'ఇండెక్స్ తొలగించు',
}, },
table_actions: { table_actions: {
@@ -174,12 +180,15 @@ export const te: LanguageTranslation = {
description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి', description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'సంబంధాలు', refs: 'Refs',
filter: 'ఫిల్టర్', filter: 'ఫిల్టర్',
add_relationship: 'సంబంధం జోడించు',
collapse: 'అన్ని కూల్ చేయి', collapse: 'అన్ని కూల్ చేయి',
add_relationship: 'సంబంధం జోడించు',
relationships: 'సంబంధాలు',
dependencies: 'ఆధారాలు',
relationship: { relationship: {
relationship: 'సంబంధం',
primary: 'ప్రాథమిక పట్టిక', primary: 'ప్రాథమిక పట్టిక',
foreign: 'సూచించబడిన పట్టిక', foreign: 'సూచించబడిన పట్టిక',
cardinality: 'కార్డినాలిటీ', cardinality: 'కార్డినాలిటీ',
@@ -189,16 +198,8 @@ export const te: LanguageTranslation = {
delete_relationship: 'సంబంధం తొలగించు', delete_relationship: 'సంబంధం తొలగించు',
}, },
}, },
empty_state: {
title: 'సంబంధాలు లేవు',
description: 'పట్టికలను అనుసంధించడానికి సంబంధం సృష్టించండి',
},
},
dependencies_section: {
dependencies: 'ఆధారాలు',
filter: 'ఫిల్టర్',
collapse: 'అన్ని కూల్ చేయి',
dependency: { dependency: {
dependency: 'ఆధారం',
table: 'పట్టిక', table: 'పట్టిక',
dependent_table: 'ఆధారిత వీక్షణ', dependent_table: 'ఆధారిత వీక్షణ',
delete_dependency: 'ఆధారాన్ని తొలగించు', delete_dependency: 'ఆధారాన్ని తొలగించు',
@@ -208,8 +209,8 @@ export const te: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'ఆధారాలు లేవు', title: 'సంబంధాలు లేవు',
description: 'ప్రారంభించడానికి ఒక వీక్షణ సృష్టించండి', description: 'ప్రారంభించడానికి ఒక సంబంధం సృష్టించండి',
}, },
}, },
@@ -249,12 +250,16 @@ export const te: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'ఏ enum విలువలు నిర్వచించబడలేదు',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -268,8 +273,14 @@ export const te: LanguageTranslation = {
show_all: 'అన్ని చూపించు', show_all: 'అన్ని చూపించు',
undo: 'తిరిగి చేయు', undo: 'తిరిగి చేయు',
redo: 'మరలా చేయు', redo: 'మరలా చేయు',
reorder_diagram: 'చిత్రాన్ని పునఃసరిచేయండి', reorder_diagram: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'అవకాశించు పట్టికలను హైలైట్ చేయండి', highlight_overlapping_tables: 'అవకాశించు పట్టికలను హైలైట్ చేయండి',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -307,7 +318,7 @@ export const te: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'చిత్రం తెరవండి', title: 'డేటాబేస్ తెరవండి',
description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.', description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.',
table_columns: { table_columns: {
name: 'పేరు', name: 'పేరు',
@@ -317,6 +328,12 @@ export const te: LanguageTranslation = {
}, },
cancel: 'రద్దు', cancel: 'రద్దు',
open: 'తెరవు', open: 'తెరవు',
diagram_actions: {
open: 'తెరవు',
duplicate: 'నకలు',
delete: 'తొలగించు',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -403,6 +420,14 @@ export const te: LanguageTranslation = {
confirm: 'మార్చు', confirm: 'మార్చు',
}, },
create_table_schema_dialog: {
title: 'కొత్త స్కీమా సృష్టించండి',
description:
'ఇంకా ఏ స్కీమాలు లేవు. మీ పట్టికలను వ్యవస్థీకరించడానికి మీ మొదటి స్కీమాను సృష్టించండి.',
create: 'సృష్టించు',
cancel: 'రద్దు',
},
star_us_dialog: { star_us_dialog: {
title: 'మా సహాయంతో మెరుగుపరచండి!', title: 'మా సహాయంతో మెరుగుపరచండి!',
description: description:
@@ -461,6 +486,7 @@ export const te: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'కొత్త పట్టిక', new_table: 'కొత్త పట్టిక',
new_view: 'కొత్త వ్యూ',
new_relationship: 'కొత్త సంబంధం', new_relationship: 'కొత్త సంబంధం',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -484,6 +510,9 @@ export const te: LanguageTranslation = {
language_select: { language_select: {
change_language: 'భాష మార్చు', change_language: 'భాష మార్చు',
}, },
on: 'ఆన్',
off: 'ఆఫ్',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const tr: LanguageTranslation = { export const tr: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Yeni',
browse: 'Gözat',
tables: 'Tablolar',
refs: 'Refs',
areas: 'Alanlar',
dependencies: 'Bağımlılıklar',
custom_types: 'Özel Tipler',
},
menu: { menu: {
file: { actions: {
file: 'Dosya', actions: 'Eylemler',
new: 'Yeni', new: 'Yeni...',
open: '', browse: 'Gözat...',
save: 'Kaydet', save: 'Kaydet',
import: 'Veritabanı İçe Aktar', import: 'Veritabanı İçe Aktar',
export_sql: 'SQL Olarak Dışa Aktar', export_sql: 'SQL Olarak Dışa Aktar',
export_as: 'Olarak Dışa Aktar', export_as: 'Olarak Dışa Aktar',
delete_diagram: 'Diyagramı Sil', delete_diagram: 'Sil',
exit: ıkış',
}, },
edit: { edit: {
edit: 'Düzenle', edit: 'Düzenle',
@@ -26,7 +34,10 @@ export const tr: LanguageTranslation = {
hide_sidebar: 'Kenar Çubuğunu Gizle', hide_sidebar: 'Kenar Çubuğunu Gizle',
hide_cardinality: 'Kardinaliteyi Gizle', hide_cardinality: 'Kardinaliteyi Gizle',
show_cardinality: 'Kardinaliteyi Göster', show_cardinality: 'Kardinaliteyi Göster',
show_field_attributes: 'Alan Özelliklerini Göster',
hide_field_attributes: 'Alan Özelliklerini Gizle',
zoom_on_scroll: 'Kaydırarak Yakınlaştır', zoom_on_scroll: 'Kaydırarak Yakınlaştır',
show_views: 'Veritabanı Görünümleri',
theme: 'Tema', theme: 'Tema',
show_dependencies: 'Bağımlılıkları Göster', show_dependencies: 'Bağımlılıkları Göster',
hide_dependencies: 'Bağımlılıkları Gizle', hide_dependencies: 'Bağımlılıkları Gizle',
@@ -64,22 +75,13 @@ export const tr: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Diyagramı Yeniden Sırala', title: 'Diyagramı Otomatik Düzenle',
description: description:
'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?', 'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?',
reorder: 'Yeniden Sırala', reorder: 'Otomatik Düzenle',
cancel: 'İptal', cancel: 'İptal',
}, },
multiple_schemas_alert: {
title: 'Birden Fazla Şema',
description:
'Bu diyagramda {{schemasCount}} şema var. Şu anda görüntülenen: {{formattedSchemas}}.',
dont_show_again: 'Tekrar gösterme',
change_schema: 'Değiştir',
none: 'yok',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Kopyalama başarısız', title: 'Kopyalama başarısız',
@@ -113,14 +115,11 @@ export const tr: LanguageTranslation = {
copy_to_clipboard: 'Panoya Kopyala', copy_to_clipboard: 'Panoya Kopyala',
copied: 'Kopyalandı!', copied: 'Kopyalandı!',
side_panel: { side_panel: {
schema: 'Şema:',
filter_by_schema: 'Şemaya Göre Filtrele',
search_schema: 'Şema ara...',
no_schemas_found: 'Şema bulunamadı.',
view_all_options: 'Tüm Seçenekleri Gör...', view_all_options: 'Tüm Seçenekleri Gör...',
tables_section: { tables_section: {
tables: 'Tablolar', tables: 'Tablolar',
add_table: 'Tablo Ekle', add_table: 'Tablo Ekle',
add_view: 'Görünüm Ekle',
filter: 'Filtrele', filter: 'Filtrele',
collapse: 'Hepsini Daralt', collapse: 'Hepsini Daralt',
// TODO: Translate // TODO: Translate
@@ -146,16 +145,23 @@ export const tr: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Alan Özellikleri', title: 'Alan Özellikleri',
unique: 'Tekil', unique: 'Tekil',
auto_increment: 'Otomatik Artış',
comments: 'Yorumlar', comments: 'Yorumlar',
no_comments: 'Yorum yok', no_comments: 'Yorum yok',
delete_field: 'Alanı Sil', delete_field: 'Alanı Sil',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Hassasiyet',
scale: 'Ölçek',
}, },
index_actions: { index_actions: {
title: 'İndeks Özellikleri', title: 'İndeks Özellikleri',
name: 'Ad', name: 'Ad',
unique: 'Tekil', unique: 'Tekil',
index_type: 'İndeks Türü',
delete_index: 'İndeksi Sil', delete_index: 'İndeksi Sil',
}, },
table_actions: { table_actions: {
@@ -173,12 +179,15 @@ export const tr: LanguageTranslation = {
description: 'Başlamak için bir tablo oluşturun', description: 'Başlamak için bir tablo oluşturun',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'İlişkiler', refs: 'Refs',
filter: 'Filtrele', filter: 'Filtrele',
add_relationship: 'İlişki Ekle',
collapse: 'Hepsini Daralt', collapse: 'Hepsini Daralt',
add_relationship: 'İlişki Ekle',
relationships: 'İlişkiler',
dependencies: 'Bağımlılıklar',
relationship: { relationship: {
relationship: 'İlişki',
primary: 'Birincil Tablo', primary: 'Birincil Tablo',
foreign: 'Referans Tablo', foreign: 'Referans Tablo',
cardinality: 'Kardinalite', cardinality: 'Kardinalite',
@@ -188,16 +197,8 @@ export const tr: LanguageTranslation = {
delete_relationship: 'Sil', delete_relationship: 'Sil',
}, },
}, },
empty_state: {
title: 'İlişki yok',
description: 'Tabloları bağlamak için bir ilişki oluşturun',
},
},
dependencies_section: {
dependencies: 'Bağımlılıklar',
filter: 'Filtrele',
collapse: 'Hepsini Daralt',
dependency: { dependency: {
dependency: 'Bağımlılık',
table: 'Tablo', table: 'Tablo',
dependent_table: 'Bağımlı Görünüm', dependent_table: 'Bağımlı Görünüm',
delete_dependency: 'Sil', delete_dependency: 'Sil',
@@ -207,8 +208,8 @@ export const tr: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Bağımlılık yok', title: 'İlişki yok',
description: 'Başlamak için bir görünüm oluşturun', description: 'Başlamak için bir ilişki oluşturun',
}, },
}, },
@@ -248,12 +249,16 @@ export const tr: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Tanımlanmış enum değeri yok',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -266,8 +271,14 @@ export const tr: LanguageTranslation = {
show_all: 'Hepsini Gör', show_all: 'Hepsini Gör',
undo: 'Geri Al', undo: 'Geri Al',
redo: 'Yinele', redo: 'Yinele',
reorder_diagram: 'Diyagramı Yeniden Sırala', reorder_diagram: 'Diyagramı Otomatik Düzenle',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Çakışan Tabloları Vurgula', highlight_overlapping_tables: 'Çakışan Tabloları Vurgula',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
database_selection: { database_selection: {
@@ -302,7 +313,7 @@ export const tr: LanguageTranslation = {
import: 'İçe Aktar', import: 'İçe Aktar',
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Diyagramı Aç', title: 'Veritabanı Aç',
description: 'Aşağıdaki listeden açmak için bir diyagram seçin.', description: 'Aşağıdaki listeden açmak için bir diyagram seçin.',
table_columns: { table_columns: {
name: 'Ad', name: 'Ad',
@@ -312,6 +323,12 @@ export const tr: LanguageTranslation = {
}, },
cancel: 'İptal', cancel: 'İptal',
open: 'Aç', open: 'Aç',
diagram_actions: {
open: 'Aç',
duplicate: 'Kopyala',
delete: 'Sil',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -392,6 +409,14 @@ export const tr: LanguageTranslation = {
cancel: 'İptal', cancel: 'İptal',
confirm: 'Değiştir', confirm: 'Değiştir',
}, },
create_table_schema_dialog: {
title: 'Yeni Şema Oluştur',
description:
'Henüz hiç şema mevcut değil. Tablolarınızı düzenlemek için ilk şemanızı oluşturun.',
create: 'Oluştur',
cancel: 'İptal',
},
star_us_dialog: { star_us_dialog: {
title: 'Bize yardım et!', title: 'Bize yardım et!',
description: description:
@@ -446,6 +471,7 @@ export const tr: LanguageTranslation = {
}, },
canvas_context_menu: { canvas_context_menu: {
new_table: 'Yeni Tablo', new_table: 'Yeni Tablo',
new_view: 'Yeni Görünüm',
new_relationship: 'Yeni İlişki', new_relationship: 'Yeni İlişki',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -468,6 +494,9 @@ export const tr: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Dil', change_language: 'Dil',
}, },
on: 'Açık',
off: 'Kapalı',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const uk: LanguageTranslation = { export const uk: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Нова',
browse: 'Огляд',
tables: 'Таблиці',
refs: 'Зв’язки',
areas: 'Області',
dependencies: 'Залежності',
custom_types: 'Користувацькі типи',
},
menu: { menu: {
file: { actions: {
file: 'Файл', actions: 'Дії',
new: 'Новий', new: 'Нова...',
open: 'Відкрити', browse: 'Огляд...',
save: 'Зберегти', save: 'Зберегти',
import: 'Імпорт бази даних', import: 'Імпорт бази даних',
export_sql: 'Експорт SQL', export_sql: 'Експорт SQL',
export_as: 'Експортувати як', export_as: 'Експортувати як',
delete_diagram: 'Видалити діаграму', delete_diagram: 'Видалити',
exit: 'Вийти',
}, },
edit: { edit: {
edit: 'Редагувати', edit: 'Редагувати',
@@ -26,7 +34,10 @@ export const uk: LanguageTranslation = {
hide_sidebar: 'Приховати бічну панель', hide_sidebar: 'Приховати бічну панель',
hide_cardinality: 'Приховати потужність', hide_cardinality: 'Приховати потужність',
show_cardinality: 'Показати кардинальність', show_cardinality: 'Показати кардинальність',
show_field_attributes: 'Показати атрибути полів',
hide_field_attributes: 'Приховати атрибути полів',
zoom_on_scroll: 'Масштабувати прокручуванням', zoom_on_scroll: 'Масштабувати прокручуванням',
show_views: 'Представлення бази даних',
theme: 'Тема', theme: 'Тема',
show_dependencies: 'Показати залежності', show_dependencies: 'Показати залежності',
hide_dependencies: 'Приховати залежності', hide_dependencies: 'Приховати залежності',
@@ -62,22 +73,13 @@ export const uk: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Перевпорядкувати діаграму', title: 'Автоматичне розміщення діаграми',
description: description:
'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?', 'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?',
reorder: 'Перевпорядкувати', reorder: 'Автоматичне розміщення',
cancel: 'Скасувати', cancel: 'Скасувати',
}, },
multiple_schemas_alert: {
title: 'Кілька схем',
description:
'{{schemasCount}} схеми на цій діаграмі. Зараз відображається: {{formattedSchemas}}.',
dont_show_again: 'Більше не показувати',
change_schema: 'Зміна',
none: 'немає',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Помилка копіювання', title: 'Помилка копіювання',
@@ -112,14 +114,11 @@ export const uk: LanguageTranslation = {
copied: 'Скопійовано!', copied: 'Скопійовано!',
side_panel: { side_panel: {
schema: 'Схема:',
filter_by_schema: 'Фільтрувати за схемою',
search_schema: 'Пошук схеми…',
no_schemas_found: 'Схеми не знайдено.',
view_all_options: 'Переглянути всі параметри…', view_all_options: 'Переглянути всі параметри…',
tables_section: { tables_section: {
tables: 'Таблиці', tables: 'Таблиці',
add_table: 'Додати таблицю', add_table: 'Додати таблицю',
add_view: 'Додати представлення',
filter: 'Фільтр', filter: 'Фільтр',
collapse: 'Згорнути все', collapse: 'Згорнути все',
// TODO: Translate // TODO: Translate
@@ -145,16 +144,23 @@ export const uk: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Атрибути полів', title: 'Атрибути полів',
unique: 'Унікальне', unique: 'Унікальне',
auto_increment: 'Автоінкремент',
comments: 'Коментарі', comments: 'Коментарі',
no_comments: 'Немає коментарів', no_comments: 'Немає коментарів',
delete_field: 'Видалити поле', delete_field: 'Видалити поле',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Точність',
scale: 'Масштаб',
}, },
index_actions: { index_actions: {
title: 'Атрибути індексу', title: 'Атрибути індексу',
name: 'Назва індекса', name: 'Назва індекса',
unique: 'Унікальний', unique: 'Унікальний',
index_type: 'Тип індексу',
delete_index: 'Видалити індекс', delete_index: 'Видалити індекс',
}, },
table_actions: { table_actions: {
@@ -171,12 +177,15 @@ export const uk: LanguageTranslation = {
description: 'Щоб почати, створіть таблицю', description: 'Щоб почати, створіть таблицю',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Звʼязки', refs: 'Refs',
filter: 'Фільтр', filter: 'Фільтр',
add_relationship: 'Додати звʼязок',
collapse: 'Згорнути все', collapse: 'Згорнути все',
add_relationship: 'Додати звʼязок',
relationships: 'Звʼязки',
dependencies: 'Залежності',
relationship: { relationship: {
relationship: 'Звʼязок',
primary: 'Первинна таблиця', primary: 'Первинна таблиця',
foreign: 'Посилання на таблицю', foreign: 'Посилання на таблицю',
cardinality: 'Звʼязок', cardinality: 'Звʼязок',
@@ -186,16 +195,8 @@ export const uk: LanguageTranslation = {
delete_relationship: 'Видалити', delete_relationship: 'Видалити',
}, },
}, },
empty_state: {
title: 'Звʼязків немає',
description: 'Створіть звʼязок для зʼєднання таблиць',
},
},
dependencies_section: {
dependencies: 'Залежності',
filter: 'Фільтр',
collapse: 'Згорнути все',
dependency: { dependency: {
dependency: 'Залежність',
table: 'Таблиця', table: 'Таблиця',
dependent_table: 'Залежне подання', dependent_table: 'Залежне подання',
delete_dependency: 'Видалити', delete_dependency: 'Видалити',
@@ -205,8 +206,8 @@ export const uk: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Жодних залежностей', title: 'Жодних зв’язків',
description: 'Створіть подання, щоб почати', description: 'Створіть зв’язок, щоб почати',
}, },
}, },
@@ -246,12 +247,16 @@ export const uk: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Значення переліку не визначені',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -265,8 +270,14 @@ export const uk: LanguageTranslation = {
show_all: 'Показати все', show_all: 'Показати все',
undo: 'Скасувати', undo: 'Скасувати',
redo: 'Повторити', redo: 'Повторити',
reorder_diagram: 'Перевпорядкувати діаграму', reorder_diagram: 'Автоматичне розміщення діаграми',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Показати таблиці, що перекриваються', highlight_overlapping_tables: 'Показати таблиці, що перекриваються',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -303,7 +314,7 @@ export const uk: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Відкрити діаграму', title: 'Відкрити базу даних',
description: description:
'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.', 'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.',
table_columns: { table_columns: {
@@ -314,6 +325,12 @@ export const uk: LanguageTranslation = {
}, },
cancel: 'Скасувати', cancel: 'Скасувати',
open: 'Відкрити', open: 'Відкрити',
diagram_actions: {
open: 'Відкрити',
duplicate: 'Дублювати',
delete: 'Видалити',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -400,6 +417,14 @@ export const uk: LanguageTranslation = {
confirm: 'Змінити', confirm: 'Змінити',
}, },
create_table_schema_dialog: {
title: 'Створити нову схему',
description:
'Поки що не існує жодної схеми. Створіть свою першу схему, щоб організувати ваші таблиці.',
create: 'Створити',
cancel: 'Скасувати',
},
star_us_dialog: { star_us_dialog: {
title: 'Допоможіть нам покращитися!', title: 'Допоможіть нам покращитися!',
description: 'Поставне на зірку на GitHub? Це лише один клік!', description: 'Поставне на зірку на GitHub? Це лише один клік!',
@@ -452,6 +477,7 @@ export const uk: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Нова таблиця', new_table: 'Нова таблиця',
new_view: 'Нове представлення',
new_relationship: 'Новий звʼязок', new_relationship: 'Новий звʼязок',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -473,6 +499,9 @@ export const uk: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Мова', change_language: 'Мова',
}, },
on: 'Увімк',
off: 'Вимк',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const vi: LanguageTranslation = { export const vi: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: 'Mới',
browse: 'Duyệt',
tables: 'Bảng',
refs: 'Refs',
areas: 'Khu vực',
dependencies: 'Phụ thuộc',
custom_types: 'Kiểu tùy chỉnh',
},
menu: { menu: {
file: { actions: {
file: 'Tệp', actions: 'Hành động',
new: 'Tạo mới', new: 'Mới...',
open: 'Mở', browse: 'Duyệt...',
save: 'Lưu', save: 'Lưu',
import: 'Nhập cơ sở dữ liệu', import: 'Nhập cơ sở dữ liệu',
export_sql: 'Xuất SQL', export_sql: 'Xuất SQL',
export_as: 'Xuất thành', export_as: 'Xuất thành',
delete_diagram: 'Xóa sơ đồ', delete_diagram: 'Xóa',
exit: 'Thoát',
}, },
edit: { edit: {
edit: 'Sửa', edit: 'Sửa',
@@ -26,7 +34,10 @@ export const vi: LanguageTranslation = {
hide_sidebar: 'Ẩn thanh bên', hide_sidebar: 'Ẩn thanh bên',
hide_cardinality: 'Ẩn số lượng', hide_cardinality: 'Ẩn số lượng',
show_cardinality: 'Hiển thị số lượng', show_cardinality: 'Hiển thị số lượng',
show_field_attributes: 'Hiển thị thuộc tính trường',
hide_field_attributes: 'Ẩn thuộc tính trường',
zoom_on_scroll: 'Thu phóng khi cuộn', zoom_on_scroll: 'Thu phóng khi cuộn',
show_views: 'Chế độ xem Cơ sở dữ liệu',
theme: 'Chủ đề', theme: 'Chủ đề',
show_dependencies: 'Hiển thị các phụ thuộc', show_dependencies: 'Hiển thị các phụ thuộc',
hide_dependencies: 'Ẩn các phụ thuộc', hide_dependencies: 'Ẩn các phụ thuộc',
@@ -63,22 +74,13 @@ export const vi: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: 'Sắp xếp lại sơ đồ', title: 'Tự động sắp xếp sơ đồ',
description: description:
'Hành động này sẽ sắp xếp lại tất cả các bảng trong sơ đồ. Bạn có muốn tiếp tục không?', 'Hành động này sẽ sắp xếp lại tất cả các bảng trong sơ đồ. Bạn có muốn tiếp tục không?',
reorder: 'Sắp xếp', reorder: 'Tự động sắp xếp',
cancel: 'Hủy', cancel: 'Hủy',
}, },
multiple_schemas_alert: {
title: 'Có nhiều lược đồ',
description:
'Có {{schemasCount}} lược đồ trong sơ đồ này. Hiện đang hiển thị: {{formattedSchemas}}.',
dont_show_again: 'Không hiển thị lại',
change_schema: 'Thay đổi',
none: 'không có',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: 'Sao chép thất bại', title: 'Sao chép thất bại',
@@ -113,14 +115,11 @@ export const vi: LanguageTranslation = {
copied: 'Đã sao chép!', copied: 'Đã sao chép!',
side_panel: { side_panel: {
schema: 'Lược đồ:',
filter_by_schema: 'Lọc bởi lược đồ',
search_schema: 'Tìm kiếm lược đồ...',
no_schemas_found: 'Không tìm thấy lược đồ.',
view_all_options: 'Xem tất cả tùy chọn...', view_all_options: 'Xem tất cả tùy chọn...',
tables_section: { tables_section: {
tables: 'Bảng', tables: 'Bảng',
add_table: 'Thêm bảng', add_table: 'Thêm bảng',
add_view: 'Thêm Chế độ xem',
filter: 'Lọc', filter: 'Lọc',
collapse: 'Thu gọn tất cả', collapse: 'Thu gọn tất cả',
// TODO: Translate // TODO: Translate
@@ -146,16 +145,23 @@ export const vi: LanguageTranslation = {
field_actions: { field_actions: {
title: 'Thuộc tính trường', title: 'Thuộc tính trường',
unique: 'Giá trị duy nhất', unique: 'Giá trị duy nhất',
auto_increment: 'Tự động tăng',
comments: 'Bình luận', comments: 'Bình luận',
no_comments: 'Không có bình luận', no_comments: 'Không có bình luận',
delete_field: 'Xóa trường', delete_field: 'Xóa trường',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: 'Độ chính xác',
scale: 'Tỷ lệ',
}, },
index_actions: { index_actions: {
title: 'Thuộc tính chỉ mục', title: 'Thuộc tính chỉ mục',
name: 'Tên', name: 'Tên',
unique: 'Giá trị duy nhất', unique: 'Giá trị duy nhất',
index_type: 'Loại chỉ mục',
delete_index: 'Xóa chỉ mục', delete_index: 'Xóa chỉ mục',
}, },
table_actions: { table_actions: {
@@ -172,12 +178,15 @@ export const vi: LanguageTranslation = {
description: 'Tạo một bảng để bắt đầu', description: 'Tạo một bảng để bắt đầu',
}, },
}, },
relationships_section: { refs_section: {
relationships: 'Quan hệ', refs: 'Refs',
filter: 'Lọc', filter: 'Lọc',
add_relationship: 'Thêm quan hệ',
collapse: 'Thu gọn tất cả', collapse: 'Thu gọn tất cả',
add_relationship: 'Thêm quan hệ',
relationships: 'Quan hệ',
dependencies: 'Phụ thuộc',
relationship: { relationship: {
relationship: 'Quan hệ',
primary: 'Bảng khóa chính', primary: 'Bảng khóa chính',
foreign: 'Bảng khóa ngoại', foreign: 'Bảng khóa ngoại',
cardinality: 'Quan hệ', cardinality: 'Quan hệ',
@@ -187,16 +196,8 @@ export const vi: LanguageTranslation = {
delete_relationship: 'Xóa', delete_relationship: 'Xóa',
}, },
}, },
empty_state: {
title: 'Không có quan hệ',
description: 'Tạo quan hệ để kết nối các bảng',
},
},
dependencies_section: {
dependencies: 'Phụ thuộc',
filter: 'Lọc',
collapse: 'Thu gọn tất cả',
dependency: { dependency: {
dependency: 'Phụ thuộc',
table: 'Bảng', table: 'Bảng',
dependent_table: 'Bảng xem phụ thuộc', dependent_table: 'Bảng xem phụ thuộc',
delete_dependency: 'Xóa', delete_dependency: 'Xóa',
@@ -206,8 +207,8 @@ export const vi: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: 'Không có phụ thuộc', title: 'Không có quan hệ',
description: 'Tạo bảng xem phụ thuộc để bắt đầu', description: 'Tạo một quan hệ để bắt đầu',
}, },
}, },
@@ -247,12 +248,16 @@ export const vi: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: 'Không có giá trị enum được định nghĩa',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -266,8 +271,14 @@ export const vi: LanguageTranslation = {
show_all: 'Hiển thị tất cả', show_all: 'Hiển thị tất cả',
undo: 'Hoàn tác', undo: 'Hoàn tác',
redo: 'Làm lại', redo: 'Làm lại',
reorder_diagram: 'Sắp xếp lại sơ đồ', reorder_diagram: 'Tự động sắp xếp sơ đồ',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: 'Làm nổi bật các bảng chồng chéo', highlight_overlapping_tables: 'Làm nổi bật các bảng chồng chéo',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -304,7 +315,7 @@ export const vi: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: 'Mở sơ đồ', title: 'Mở cơ sở dữ liệu',
description: 'Chọn sơ đồ để mở từ danh sách bên dưới.', description: 'Chọn sơ đồ để mở từ danh sách bên dưới.',
table_columns: { table_columns: {
name: 'Tên', name: 'Tên',
@@ -314,6 +325,12 @@ export const vi: LanguageTranslation = {
}, },
cancel: 'Hủy', cancel: 'Hủy',
open: 'Mở', open: 'Mở',
diagram_actions: {
open: 'Mở',
duplicate: 'Nhân bản',
delete: 'Xóa',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -399,6 +416,14 @@ export const vi: LanguageTranslation = {
confirm: 'Xác nhận', confirm: 'Xác nhận',
}, },
create_table_schema_dialog: {
title: 'Tạo lược đồ mới',
description:
'Chưa có lược đồ nào. Tạo lược đồ đầu tiên của bạn để tổ chức các bảng.',
create: 'Tạo',
cancel: 'Hủy',
},
star_us_dialog: { star_us_dialog: {
title: 'Hãy giúp chúng tôi cải thiện!', title: 'Hãy giúp chúng tôi cải thiện!',
description: description:
@@ -453,6 +478,7 @@ export const vi: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: 'Tạo bảng mới', new_table: 'Tạo bảng mới',
new_view: 'Chế độ xem Mới',
new_relationship: 'Tạo quan hệ mới', new_relationship: 'Tạo quan hệ mới',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -474,6 +500,9 @@ export const vi: LanguageTranslation = {
language_select: { language_select: {
change_language: 'Ngôn ngữ', change_language: 'Ngôn ngữ',
}, },
on: 'Bật',
off: 'Tắt',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const zh_CN: LanguageTranslation = { export const zh_CN: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '新建',
browse: '浏览',
tables: '表',
refs: '引用',
areas: '区域',
dependencies: '依赖关系',
custom_types: '自定义类型',
},
menu: { menu: {
file: { actions: {
file: '文件', actions: '操作',
new: '新建', new: '新建...',
open: '打开', browse: '浏览...',
save: '保存', save: '保存',
import: '导入数据库', import: '导入数据库',
export_sql: '导出 SQL 语句', export_sql: '导出 SQL 语句',
export_as: '导出为', export_as: '导出为',
delete_diagram: '删除关系图', delete_diagram: '删除',
exit: '退出',
}, },
edit: { edit: {
edit: '编辑', edit: '编辑',
@@ -26,7 +34,10 @@ export const zh_CN: LanguageTranslation = {
hide_sidebar: '隐藏侧边栏', hide_sidebar: '隐藏侧边栏',
hide_cardinality: '隐藏基数', hide_cardinality: '隐藏基数',
show_cardinality: '展示基数', show_cardinality: '展示基数',
show_field_attributes: '展示字段属性',
hide_field_attributes: '隐藏字段属性',
zoom_on_scroll: '滚动缩放', zoom_on_scroll: '滚动缩放',
show_views: '数据库视图',
theme: '主题', theme: '主题',
show_dependencies: '展示依赖', show_dependencies: '展示依赖',
hide_dependencies: '隐藏依赖', hide_dependencies: '隐藏依赖',
@@ -61,21 +72,12 @@ export const zh_CN: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: '重新排列关系图', title: '自动排列关系图',
description: '此操作将重新排列关系图中的所有表。是否要继续?', description: '此操作将重新排列关系图中的所有表。是否要继续?',
reorder: '重新排列', reorder: '自动排列',
cancel: '取消', cancel: '取消',
}, },
multiple_schemas_alert: {
title: '多个模式',
description:
'此关系图中有 {{schemasCount}} 个模式,当前显示:{{formattedSchemas}}。',
dont_show_again: '不再展示',
change_schema: '更改',
none: '无',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: '复制失败', title: '复制失败',
@@ -110,14 +112,11 @@ export const zh_CN: LanguageTranslation = {
copied: '复制了!', copied: '复制了!',
side_panel: { side_panel: {
schema: '模式:',
filter_by_schema: '按模式筛选',
search_schema: '搜索模式...',
no_schemas_found: '未找到模式。',
view_all_options: '查看所有选项...', view_all_options: '查看所有选项...',
tables_section: { tables_section: {
tables: '表', tables: '表',
add_table: '添加表', add_table: '添加表',
add_view: '添加视图',
filter: '筛选', filter: '筛选',
collapse: '全部折叠', collapse: '全部折叠',
// TODO: Translate // TODO: Translate
@@ -143,16 +142,23 @@ export const zh_CN: LanguageTranslation = {
field_actions: { field_actions: {
title: '字段属性', title: '字段属性',
unique: '唯一', unique: '唯一',
auto_increment: '自动递增',
comments: '注释', comments: '注释',
no_comments: '空', no_comments: '空',
delete_field: '删除字段', delete_field: '删除字段',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: '精度',
scale: '小数位',
}, },
index_actions: { index_actions: {
title: '索引属性', title: '索引属性',
name: '名称', name: '名称',
unique: '唯一', unique: '唯一',
index_type: '索引类型',
delete_index: '删除索引', delete_index: '删除索引',
}, },
table_actions: { table_actions: {
@@ -169,12 +175,15 @@ export const zh_CN: LanguageTranslation = {
description: '新建表以开始', description: '新建表以开始',
}, },
}, },
relationships_section: { refs_section: {
relationships: '关系', refs: '引用',
filter: '筛选', filter: '筛选',
add_relationship: '添加关系',
collapse: '全部折叠', collapse: '全部折叠',
add_relationship: '添加关系',
relationships: '关系',
dependencies: '依赖关系',
relationship: { relationship: {
relationship: '关系',
primary: '主表', primary: '主表',
foreign: '被引用表', foreign: '被引用表',
cardinality: '基数', cardinality: '基数',
@@ -184,16 +193,8 @@ export const zh_CN: LanguageTranslation = {
delete_relationship: '删除', delete_relationship: '删除',
}, },
}, },
empty_state: {
title: '无关系',
description: '创建关系以连接表',
},
},
dependencies_section: {
dependencies: '依赖关系',
filter: '筛选',
collapse: '全部折叠',
dependency: { dependency: {
dependency: '依赖',
table: '表', table: '表',
dependent_table: '依赖视图', dependent_table: '依赖视图',
delete_dependency: '删除', delete_dependency: '删除',
@@ -203,8 +204,8 @@ export const zh_CN: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '无依赖', title: '无关系',
description: '创建视图以开始', description: '创建关系以开始',
}, },
}, },
@@ -244,12 +245,16 @@ export const zh_CN: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '没有定义枚举值',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -263,8 +268,14 @@ export const zh_CN: LanguageTranslation = {
show_all: '展示全部', show_all: '展示全部',
undo: '撤销', undo: '撤销',
redo: '重做', redo: '重做',
reorder_diagram: '重新排列关系图', reorder_diagram: '自动排列关系图',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: '突出显示重叠的表', highlight_overlapping_tables: '突出显示重叠的表',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -301,7 +312,7 @@ export const zh_CN: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: '打开关系图', title: '打开数据库',
description: '从下面的列表中选择一个图表打开。', description: '从下面的列表中选择一个图表打开。',
table_columns: { table_columns: {
name: '名称', name: '名称',
@@ -311,6 +322,12 @@ export const zh_CN: LanguageTranslation = {
}, },
cancel: '取消', cancel: '取消',
open: '打开', open: '打开',
diagram_actions: {
open: '打开',
duplicate: '复制',
delete: '删除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -395,6 +412,13 @@ export const zh_CN: LanguageTranslation = {
confirm: '更改', confirm: '更改',
}, },
create_table_schema_dialog: {
title: '创建新模式',
description: '尚未存在任何模式。创建您的第一个模式来组织您的表。',
create: '创建',
cancel: '取消',
},
star_us_dialog: { star_us_dialog: {
title: '帮助我们改进!', title: '帮助我们改进!',
description: '您想在 GitHub 上为我们加注星标吗?只需点击一下即可!', description: '您想在 GitHub 上为我们加注星标吗?只需点击一下即可!',
@@ -449,6 +473,7 @@ export const zh_CN: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '新建表', new_table: '新建表',
new_view: '新建视图',
new_relationship: '新建关系', new_relationship: '新建关系',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -470,6 +495,9 @@ export const zh_CN: LanguageTranslation = {
language_select: { language_select: {
change_language: '语言', change_language: '语言',
}, },
on: '开启',
off: '关闭',
}, },
}; };

View File

@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const zh_TW: LanguageTranslation = { export const zh_TW: LanguageTranslation = {
translation: { translation: {
editor_sidebar: {
new_diagram: '新建',
browse: '瀏覽',
tables: '表格',
refs: 'Refs',
areas: '區域',
dependencies: '相依性',
custom_types: '自定義類型',
},
menu: { menu: {
file: { actions: {
file: '檔案', actions: '操作',
new: '新增', new: '新增...',
open: '開啟', browse: '瀏覽...',
save: '儲存', save: '儲存',
import: '匯入資料庫', import: '匯入資料庫',
export_sql: '匯出 SQL', export_sql: '匯出 SQL',
export_as: '匯出為特定格式', export_as: '匯出為特定格式',
delete_diagram: '刪除圖表', delete_diagram: '刪除',
exit: '退出',
}, },
edit: { edit: {
edit: '編輯', edit: '編輯',
@@ -26,7 +34,10 @@ export const zh_TW: LanguageTranslation = {
hide_sidebar: '隱藏側邊欄', hide_sidebar: '隱藏側邊欄',
hide_cardinality: '隱藏基數', hide_cardinality: '隱藏基數',
show_cardinality: '顯示基數', show_cardinality: '顯示基數',
hide_field_attributes: '隱藏欄位屬性',
show_field_attributes: '顯示欄位屬性',
zoom_on_scroll: '滾動縮放', zoom_on_scroll: '滾動縮放',
show_views: '資料庫檢視',
theme: '主題', theme: '主題',
show_dependencies: '顯示相依性', show_dependencies: '顯示相依性',
hide_dependencies: '隱藏相依性', hide_dependencies: '隱藏相依性',
@@ -61,21 +72,12 @@ export const zh_TW: LanguageTranslation = {
}, },
reorder_diagram_alert: { reorder_diagram_alert: {
title: '重新排列圖表', title: '自動排列圖表',
description: '此操作將重新排列圖表中的所有表格。是否繼續?', description: '此操作將重新排列圖表中的所有表格。是否繼續?',
reorder: '重新排列', reorder: '自動排列',
cancel: '取消', cancel: '取消',
}, },
multiple_schemas_alert: {
title: '多重 Schema',
description:
'此圖表中包含 {{schemasCount}} 個 Schema目前顯示{{formattedSchemas}}。',
dont_show_again: '不再顯示',
change_schema: '變更',
none: '無',
},
copy_to_clipboard_toast: { copy_to_clipboard_toast: {
unsupported: { unsupported: {
title: '複製失敗', title: '複製失敗',
@@ -110,14 +112,11 @@ export const zh_TW: LanguageTranslation = {
copied: '已複製!', copied: '已複製!',
side_panel: { side_panel: {
schema: 'Schema:',
filter_by_schema: '依 Schema 篩選',
search_schema: '搜尋 Schema...',
no_schemas_found: '未找到 Schema。',
view_all_options: '顯示所有選項...', view_all_options: '顯示所有選項...',
tables_section: { tables_section: {
tables: '表格', tables: '表格',
add_table: '新增表格', add_table: '新增表格',
add_view: '新增檢視',
filter: '篩選', filter: '篩選',
collapse: '全部摺疊', collapse: '全部摺疊',
// TODO: Translate // TODO: Translate
@@ -143,16 +142,23 @@ export const zh_TW: LanguageTranslation = {
field_actions: { field_actions: {
title: '欄位屬性', title: '欄位屬性',
unique: '唯一', unique: '唯一',
auto_increment: '自動遞增',
comments: '註解', comments: '註解',
no_comments: '無註解', no_comments: '無註解',
delete_field: '刪除欄位', delete_field: '刪除欄位',
// TODO: Translate // TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length', character_length: 'Max Length',
precision: '精度',
scale: '小數位',
}, },
index_actions: { index_actions: {
title: '索引屬性', title: '索引屬性',
name: '名稱', name: '名稱',
unique: '唯一', unique: '唯一',
index_type: '索引類型',
delete_index: '刪除索引', delete_index: '刪除索引',
}, },
table_actions: { table_actions: {
@@ -169,12 +175,15 @@ export const zh_TW: LanguageTranslation = {
description: '請新增表格以開始', description: '請新增表格以開始',
}, },
}, },
relationships_section: { refs_section: {
relationships: '關聯', refs: 'Refs',
filter: '篩選', filter: '篩選',
add_relationship: '新增關聯',
collapse: '全部摺疊', collapse: '全部摺疊',
add_relationship: '新增關聯',
relationships: '關聯',
dependencies: '相依性',
relationship: { relationship: {
relationship: '關聯',
primary: '主表格', primary: '主表格',
foreign: '參照表格', foreign: '參照表格',
cardinality: '基數', cardinality: '基數',
@@ -184,16 +193,8 @@ export const zh_TW: LanguageTranslation = {
delete_relationship: '刪除', delete_relationship: '刪除',
}, },
}, },
empty_state: {
title: '尚無關聯',
description: '請新增關聯以連接表格',
},
},
dependencies_section: {
dependencies: '相依性',
filter: '篩選',
collapse: '全部摺疊',
dependency: { dependency: {
dependency: '相依性',
table: '表格', table: '表格',
dependent_table: '相依檢視', dependent_table: '相依檢視',
delete_dependency: '刪除', delete_dependency: '刪除',
@@ -203,8 +204,8 @@ export const zh_TW: LanguageTranslation = {
}, },
}, },
empty_state: { empty_state: {
title: '尚無相依性', title: '尚無關聯',
description: '請建立檢視以開始', description: '請建立關聯以開始',
}, },
}, },
@@ -244,12 +245,16 @@ export const zh_TW: LanguageTranslation = {
enum_values: 'Enum Values', enum_values: 'Enum Values',
composite_fields: 'Fields', composite_fields: 'Fields',
no_fields: 'No fields defined', no_fields: 'No fields defined',
no_values: '沒有定義列舉值',
field_name_placeholder: 'Field name', field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type', field_type_placeholder: 'Select type',
add_field: 'Add Field', add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: { custom_type_actions: {
title: 'Actions', title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete', delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
}, },
delete_custom_type: 'Delete Type', delete_custom_type: 'Delete Type',
}, },
@@ -263,8 +268,14 @@ export const zh_TW: LanguageTranslation = {
show_all: '顯示全部', show_all: '顯示全部',
undo: '復原', undo: '復原',
redo: '重做', redo: '重做',
reorder_diagram: '重新排列圖表', reorder_diagram: '自動排列圖表',
// TODO: Translate
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
custom_type_highlight_tooltip:
'Highlighting "{{typeName}}" - Click to clear',
highlight_overlapping_tables: '突出顯示重疊表格', highlight_overlapping_tables: '突出顯示重疊表格',
// TODO: Translate
filter: 'Filter Tables',
}, },
new_diagram_dialog: { new_diagram_dialog: {
@@ -300,7 +311,7 @@ export const zh_TW: LanguageTranslation = {
}, },
open_diagram_dialog: { open_diagram_dialog: {
title: '開啟圖表', title: '開啟資料庫',
description: '請從以下列表中選擇一個圖表。', description: '請從以下列表中選擇一個圖表。',
table_columns: { table_columns: {
name: '名稱', name: '名稱',
@@ -310,6 +321,12 @@ export const zh_TW: LanguageTranslation = {
}, },
cancel: '取消', cancel: '取消',
open: '開啟', open: '開啟',
diagram_actions: {
open: '開啟',
duplicate: '複製',
delete: '刪除',
},
}, },
export_sql_dialog: { export_sql_dialog: {
@@ -394,6 +411,14 @@ export const zh_TW: LanguageTranslation = {
confirm: '變更', confirm: '變更',
}, },
create_table_schema_dialog: {
title: '建立新 Schema',
description:
'尚未存在任何 Schema。建立您的第一個 Schema 來組織您的表格。',
create: '建立',
cancel: '取消',
},
star_us_dialog: { star_us_dialog: {
title: '協助我們改善!', title: '協助我們改善!',
description: '請在 GitHub 上給我們一顆星,只需點擊一下!', description: '請在 GitHub 上給我們一顆星,只需點擊一下!',
@@ -448,6 +473,7 @@ export const zh_TW: LanguageTranslation = {
canvas_context_menu: { canvas_context_menu: {
new_table: '新建表格', new_table: '新建表格',
new_view: '新檢視',
new_relationship: '新建關聯', new_relationship: '新建關聯',
// TODO: Translate // TODO: Translate
new_area: 'New Area', new_area: 'New Area',
@@ -469,6 +495,9 @@ export const zh_TW: LanguageTranslation = {
language_select: { language_select: {
change_language: '變更語言', change_language: '變更語言',
}, },
on: '開啟',
off: '關閉',
}, },
}; };

View File

@@ -18,4 +18,7 @@
.marker-definitions { .marker-definitions {
} }
.nodrag {
}
} }

View File

@@ -1,3 +1,4 @@
import type { DBCustomType } from './domain';
import type { Area } from './domain/area'; import type { Area } from './domain/area';
import type { DBDependency } from './domain/db-dependency'; import type { DBDependency } from './domain/db-dependency';
import type { DBField } from './domain/db-field'; import type { DBField } from './domain/db-field';
@@ -48,6 +49,10 @@ const generateIdsMapFromDiagram = (
idsMap.set(area.id, generateId()); idsMap.set(area.id, generateId());
}); });
diagram.customTypes?.forEach((customType) => {
idsMap.set(customType.id, generateId());
});
return idsMap; return idsMap;
}; };
@@ -124,7 +129,7 @@ export const cloneDiagram = (
} = { } = {
generateId: defaultGenerateId, generateId: defaultGenerateId,
} }
): Diagram => { ): { diagram: Diagram; idsMap: Map<string, string> } => {
const { generateId } = options; const { generateId } = options;
const diagramId = generateId(); const diagramId = generateId();
@@ -213,14 +218,38 @@ export const cloneDiagram = (
}) })
.filter((area): area is Area => area !== null) ?? []; .filter((area): area is Area => area !== null) ?? [];
const customTypes: DBCustomType[] =
diagram.customTypes
?.map((customType) => {
const id = getNewId(customType.id);
if (!id) {
return null;
}
return {
...customType,
id,
} satisfies DBCustomType;
})
.filter(
(customType): customType is DBCustomType => customType !== null
) ?? [];
return { return {
...diagram, diagram: {
id: diagramId, ...diagram,
dependencies, id: diagramId,
relationships, dependencies,
tables, relationships,
areas, tables,
createdAt: new Date(), areas,
updatedAt: new Date(), customTypes,
createdAt: diagram.createdAt
? new Date(diagram.createdAt)
: new Date(),
updatedAt: diagram.updatedAt
? new Date(diagram.updatedAt)
: new Date(),
},
idsMap,
}; };
}; };

View File

@@ -19,3 +19,5 @@ export const randomColor = () => {
export const viewColor = '#b0b0b0'; export const viewColor = '#b0b0b0';
export const materializedViewColor = '#7d7d7d'; export const materializedViewColor = '#7d7d7d';
export const defaultTableColor = '#8eb7ff';
export const defaultAreaColor = '#b067e9';

View File

@@ -48,18 +48,30 @@ export const clickhouseDataTypes: readonly DataTypeData[] = [
{ name: 'mediumblob', id: 'mediumblob' }, { name: 'mediumblob', id: 'mediumblob' },
{ name: 'tinyblob', id: 'tinyblob' }, { name: 'tinyblob', id: 'tinyblob' },
{ name: 'blob', id: 'blob' }, { name: 'blob', id: 'blob' },
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true }, {
{ name: 'char', id: 'char', hasCharMaxLength: true }, name: 'varchar',
id: 'varchar',
fieldAttributes: { hasCharMaxLength: true },
},
{ name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
{ name: 'char large object', id: 'char_large_object' }, { name: 'char large object', id: 'char_large_object' },
{ name: 'char varying', id: 'char_varying', hasCharMaxLength: true }, {
name: 'char varying',
id: 'char_varying',
fieldAttributes: { hasCharMaxLength: true },
},
{ name: 'character large object', id: 'character_large_object' }, { name: 'character large object', id: 'character_large_object' },
{ {
name: 'character varying', name: 'character varying',
id: 'character_varying', id: 'character_varying',
hasCharMaxLength: true, fieldAttributes: { hasCharMaxLength: true },
}, },
{ name: 'nchar large object', id: 'nchar_large_object' }, { name: 'nchar large object', id: 'nchar_large_object' },
{ name: 'nchar varying', id: 'nchar_varying', hasCharMaxLength: true }, {
name: 'nchar varying',
id: 'nchar_varying',
fieldAttributes: { hasCharMaxLength: true },
},
{ {
name: 'national character large object', name: 'national character large object',
id: 'national_character_large_object', id: 'national_character_large_object',
@@ -67,22 +79,34 @@ export const clickhouseDataTypes: readonly DataTypeData[] = [
{ {
name: 'national character varying', name: 'national character varying',
id: 'national_character_varying', id: 'national_character_varying',
hasCharMaxLength: true, fieldAttributes: { hasCharMaxLength: true },
}, },
{ {
name: 'national char varying', name: 'national char varying',
id: 'national_char_varying', id: 'national_char_varying',
hasCharMaxLength: true, fieldAttributes: { hasCharMaxLength: true },
}, },
{ {
name: 'national character', name: 'national character',
id: 'national_character', id: 'national_character',
hasCharMaxLength: true, fieldAttributes: { hasCharMaxLength: true },
},
{
name: 'national char',
id: 'national_char',
fieldAttributes: { hasCharMaxLength: true },
}, },
{ name: 'national char', id: 'national_char', hasCharMaxLength: true },
{ name: 'binary large object', id: 'binary_large_object' }, { name: 'binary large object', id: 'binary_large_object' },
{ name: 'binary varying', id: 'binary_varying', hasCharMaxLength: true }, {
{ name: 'fixedstring', id: 'fixedstring', hasCharMaxLength: true }, name: 'binary varying',
id: 'binary_varying',
fieldAttributes: { hasCharMaxLength: true },
},
{
name: 'fixedstring',
id: 'fixedstring',
fieldAttributes: { hasCharMaxLength: true },
},
{ name: 'string', id: 'string' }, { name: 'string', id: 'string' },
// Date Types // Date Types

View File

@@ -14,9 +14,23 @@ export interface DataType {
name: string; name: string;
} }
export interface DataTypeData extends DataType { export interface FieldAttributeRange {
max: number;
min: number;
default: number;
}
interface FieldAttributes {
hasCharMaxLength?: boolean; hasCharMaxLength?: boolean;
hasCharMaxLengthOption?: boolean;
precision?: FieldAttributeRange;
scale?: FieldAttributeRange;
maxLength?: number;
}
export interface DataTypeData extends DataType {
usageLevel?: 1 | 2; // Level 1 is most common, Level 2 is second most common usageLevel?: 1 | 2; // Level 1 is most common, Level 2 is second most common
fieldAttributes?: FieldAttributes;
} }
export const dataTypeSchema: z.ZodType<DataType> = z.object({ export const dataTypeSchema: z.ZodType<DataType> = z.object({
@@ -132,3 +146,22 @@ export const findDataTypeDataById = (
return dataTypesOptions.find((dataType) => dataType.id === id); return dataTypesOptions.find((dataType) => dataType.id === id);
}; };
export const supportsAutoIncrementDataType = (
dataTypeName: string
): boolean => {
return [
'integer',
'int',
'bigint',
'smallint',
'tinyint',
'mediumint',
'serial',
'bigserial',
'smallserial',
'number',
'numeric',
'decimal',
].includes(dataTypeName.toLocaleLowerCase());
};

View File

@@ -2,7 +2,12 @@ import type { DataTypeData } from './data-types';
export const genericDataTypes: readonly DataTypeData[] = [ export const genericDataTypes: readonly DataTypeData[] = [
// Level 1 - Most commonly used types // Level 1 - Most commonly used types
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 }, {
name: 'varchar',
id: 'varchar',
fieldAttributes: { hasCharMaxLength: true },
usageLevel: 1,
},
{ name: 'int', id: 'int', usageLevel: 1 }, { name: 'int', id: 'int', usageLevel: 1 },
{ name: 'text', id: 'text', usageLevel: 1 }, { name: 'text', id: 'text', usageLevel: 1 },
{ name: 'boolean', id: 'boolean', usageLevel: 1 }, { name: 'boolean', id: 'boolean', usageLevel: 1 },
@@ -10,23 +15,62 @@ export const genericDataTypes: readonly DataTypeData[] = [
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 }, { name: 'timestamp', id: 'timestamp', usageLevel: 1 },
// Level 2 - Second most common types // Level 2 - Second most common types
{ name: 'decimal', id: 'decimal', usageLevel: 2 }, {
name: 'decimal',
id: 'decimal',
usageLevel: 2,
fieldAttributes: {
precision: {
max: 999,
min: 1,
default: 10,
},
scale: {
max: 999,
min: 0,
default: 2,
},
},
},
{ name: 'datetime', id: 'datetime', usageLevel: 2 }, { name: 'datetime', id: 'datetime', usageLevel: 2 },
{ name: 'json', id: 'json', usageLevel: 2 }, { name: 'json', id: 'json', usageLevel: 2 },
{ name: 'uuid', id: 'uuid', usageLevel: 2 }, { name: 'uuid', id: 'uuid', usageLevel: 2 },
// Less common types // Less common types
{ name: 'bigint', id: 'bigint' }, { name: 'bigint', id: 'bigint' },
{ name: 'binary', id: 'binary', hasCharMaxLength: true }, {
name: 'binary',
id: 'binary',
fieldAttributes: { hasCharMaxLength: true },
},
{ name: 'blob', id: 'blob' }, { name: 'blob', id: 'blob' },
{ name: 'char', id: 'char', hasCharMaxLength: true }, { name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
{ name: 'double', id: 'double' }, { name: 'double', id: 'double' },
{ name: 'enum', id: 'enum' }, { name: 'enum', id: 'enum' },
{ name: 'float', id: 'float' }, { name: 'float', id: 'float' },
{ name: 'numeric', id: 'numeric' }, {
name: 'numeric',
id: 'numeric',
fieldAttributes: {
precision: {
max: 999,
min: 1,
default: 10,
},
scale: {
max: 999,
min: 0,
default: 2,
},
},
},
{ name: 'real', id: 'real' }, { name: 'real', id: 'real' },
{ name: 'set', id: 'set' }, { name: 'set', id: 'set' },
{ name: 'smallint', id: 'smallint' }, { name: 'smallint', id: 'smallint' },
{ name: 'time', id: 'time' }, { name: 'time', id: 'time' },
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true }, {
name: 'varbinary',
id: 'varbinary',
fieldAttributes: { hasCharMaxLength: true },
},
] as const; ] as const;

View File

@@ -4,12 +4,32 @@ export const mariadbDataTypes: readonly DataTypeData[] = [
// Level 1 - Most commonly used types // Level 1 - Most commonly used types
{ name: 'int', id: 'int', usageLevel: 1 }, { name: 'int', id: 'int', usageLevel: 1 },
{ name: 'bigint', id: 'bigint', usageLevel: 1 }, { name: 'bigint', id: 'bigint', usageLevel: 1 },
{ name: 'decimal', id: 'decimal', usageLevel: 1 }, {
name: 'decimal',
id: 'decimal',
usageLevel: 1,
fieldAttributes: {
precision: {
max: 65,
min: 1,
default: 10,
},
scale: {
max: 30,
min: 0,
default: 0,
},
},
},
{ name: 'boolean', id: 'boolean', usageLevel: 1 }, { name: 'boolean', id: 'boolean', usageLevel: 1 },
{ name: 'datetime', id: 'datetime', usageLevel: 1 }, { name: 'datetime', id: 'datetime', usageLevel: 1 },
{ name: 'date', id: 'date', usageLevel: 1 }, { name: 'date', id: 'date', usageLevel: 1 },
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 }, { name: 'timestamp', id: 'timestamp', usageLevel: 1 },
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 }, {
name: 'varchar',
id: 'varchar',
fieldAttributes: { hasCharMaxLength: true },
},
{ name: 'text', id: 'text', usageLevel: 1 }, { name: 'text', id: 'text', usageLevel: 1 },
// Level 2 - Second most common types // Level 2 - Second most common types
@@ -20,16 +40,39 @@ export const mariadbDataTypes: readonly DataTypeData[] = [
{ name: 'tinyint', id: 'tinyint' }, { name: 'tinyint', id: 'tinyint' },
{ name: 'smallint', id: 'smallint' }, { name: 'smallint', id: 'smallint' },
{ name: 'mediumint', id: 'mediumint' }, { name: 'mediumint', id: 'mediumint' },
{ name: 'numeric', id: 'numeric' }, {
name: 'numeric',
id: 'numeric',
fieldAttributes: {
precision: {
max: 65,
min: 1,
default: 10,
},
scale: {
max: 30,
min: 0,
default: 0,
},
},
},
{ name: 'float', id: 'float' }, { name: 'float', id: 'float' },
{ name: 'double', id: 'double' }, { name: 'double', id: 'double' },
{ name: 'bit', id: 'bit' }, { name: 'bit', id: 'bit' },
{ name: 'bool', id: 'bool' }, { name: 'bool', id: 'bool' },
{ name: 'time', id: 'time' }, { name: 'time', id: 'time' },
{ name: 'year', id: 'year' }, { name: 'year', id: 'year' },
{ name: 'char', id: 'char', hasCharMaxLength: true }, { name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
{ name: 'binary', id: 'binary', hasCharMaxLength: true }, {
{ name: 'varbinary', id: 'varbinary', hasCharMaxLength: true }, name: 'binary',
id: 'binary',
fieldAttributes: { hasCharMaxLength: true },
},
{
name: 'varbinary',
id: 'varbinary',
fieldAttributes: { hasCharMaxLength: true },
},
{ name: 'tinyblob', id: 'tinyblob' }, { name: 'tinyblob', id: 'tinyblob' },
{ name: 'blob', id: 'blob' }, { name: 'blob', id: 'blob' },
{ name: 'mediumblob', id: 'mediumblob' }, { name: 'mediumblob', id: 'mediumblob' },

View File

@@ -3,7 +3,12 @@ import type { DataTypeData } from './data-types';
export const mysqlDataTypes: readonly DataTypeData[] = [ export const mysqlDataTypes: readonly DataTypeData[] = [
// Level 1 - Most commonly used types // Level 1 - Most commonly used types
{ name: 'int', id: 'int', usageLevel: 1 }, { name: 'int', id: 'int', usageLevel: 1 },
{ name: 'varchar', id: 'varchar', hasCharMaxLength: true, usageLevel: 1 }, {
name: 'varchar',
id: 'varchar',
fieldAttributes: { hasCharMaxLength: true },
usageLevel: 1,
},
{ name: 'text', id: 'text', usageLevel: 1 }, { name: 'text', id: 'text', usageLevel: 1 },
{ name: 'boolean', id: 'boolean', usageLevel: 1 }, { name: 'boolean', id: 'boolean', usageLevel: 1 },
{ name: 'timestamp', id: 'timestamp', usageLevel: 1 }, { name: 'timestamp', id: 'timestamp', usageLevel: 1 },
@@ -11,7 +16,23 @@ export const mysqlDataTypes: readonly DataTypeData[] = [
// Level 2 - Second most common types // Level 2 - Second most common types
{ name: 'bigint', id: 'bigint', usageLevel: 2 }, { name: 'bigint', id: 'bigint', usageLevel: 2 },
{ name: 'decimal', id: 'decimal', usageLevel: 2 }, {
name: 'decimal',
id: 'decimal',
usageLevel: 2,
fieldAttributes: {
precision: {
max: 65,
min: 1,
default: 10,
},
scale: {
max: 30,
min: 0,
default: 0,
},
},
},
{ name: 'datetime', id: 'datetime', usageLevel: 2 }, { name: 'datetime', id: 'datetime', usageLevel: 2 },
{ name: 'json', id: 'json', usageLevel: 2 }, { name: 'json', id: 'json', usageLevel: 2 },
@@ -22,7 +43,7 @@ export const mysqlDataTypes: readonly DataTypeData[] = [
{ name: 'float', id: 'float' }, { name: 'float', id: 'float' },
{ name: 'double', id: 'double' }, { name: 'double', id: 'double' },
{ name: 'bit', id: 'bit' }, { name: 'bit', id: 'bit' },
{ name: 'char', id: 'char', hasCharMaxLength: true }, { name: 'char', id: 'char', fieldAttributes: { hasCharMaxLength: true } },
{ name: 'tinytext', id: 'tinytext' }, { name: 'tinytext', id: 'tinytext' },
{ name: 'mediumtext', id: 'mediumtext' }, { name: 'mediumtext', id: 'mediumtext' },
{ name: 'longtext', id: 'longtext' }, { name: 'longtext', id: 'longtext' },

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