Compare commits

..

3 Commits

Author SHA1 Message Date
johnnyfish
2f5533c071 feat: add MySQL & MariaDB tests and fix parsing SQL 2025-07-13 08:58:59 +03:00
johnnyfish
5b9a88a8f3 fix: disable format on paste for SQL DDL import 2025-07-12 11:16:16 +03:00
johnnyfish
2a8714a564 feat: add PostgreSQL tests and fix parsing SQL 2025-07-12 11:14:42 +03:00
347 changed files with 17019 additions and 121969 deletions

View File

@@ -24,7 +24,4 @@ jobs:
run: npm run lint
- name: Build
run: npm run build
- name: Run tests
run: npm run test:ci
run: npm run build

View File

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

97
.husky/README.md Normal file
View File

@@ -0,0 +1,97 @@
# Smart Pre-commit Hooks
This directory contains intelligent pre-commit hooks that run relevant tests based on the files being committed.
## Features
- **Smart Test Detection**: Automatically detects which tests to run based on changed files
- **Configurable Mappings**: Easy to configure via `test-mapping.json` (optional)
- **Performance Optimized**: Only runs tests for affected code
- **Skip Option**: Temporarily skip tests when needed
- **Progressive Enhancement**: Works without dependencies, enhanced with `jq` if available
## How It Works
1. **Linting**: Always runs linting first
2. **File Analysis**: Examines staged files to determine which are SQL import related
3. **Test Selection**: Maps changed files to relevant test suites
4. **Test Execution**: Runs only the necessary tests
## Configuration
The test runner works in two modes:
### Basic Mode (No Dependencies)
- Uses built-in patterns for common SQL import files
- Works out of the box without any additional tools
### Enhanced Mode (With `jq`)
- Reads configuration from `test-mapping.json`
- Allows custom patterns and mappings
- More flexible and maintainable
### Automatic Behaviors
- **Documentation Changes**: Tests are automatically skipped for .md, .txt, and .rst files
- **Verbose Output**: Always shows matched files and test paths for better visibility
## File Mappings
Built-in mappings:
- PostgreSQL import files → PostgreSQL tests
- MySQL import files → MySQL tests
- SQLite import files → SQLite tests
- SQL Server import files → SQL Server tests
- Common SQL files → All dialect tests
- SQL validator → PostgreSQL tests
## Usage
### Normal Operation
Just commit as usual. The hooks will automatically run relevant tests.
### Skip Tests Temporarily
```bash
# Create skip file
touch .husky/.skip-tests
# Commit without tests
git commit -m "WIP: debugging"
# Remove skip file to re-enable
rm .husky/.skip-tests
```
### Customize Mappings
1. Install `jq`: `brew install jq` (macOS) or `apt-get install jq` (Linux)
2. Edit `test-mapping.json` to add new patterns or modify existing ones
## Requirements
- **Required**: None (works with bash only)
- **Optional**: `jq` for JSON configuration support
## Examples
### Example 1: PostgreSQL Parser Change
```bash
# Changed: src/lib/data/sql-import/dialect-importers/postgresql/postgresql-improved.ts
# Runs: src/lib/data/sql-import/dialect-importers/postgresql/__tests__
```
### Example 2: Common SQL Import Change
```bash
# Changed: src/lib/data/sql-import/common.ts
# Runs: All dialect tests (PostgreSQL, MySQL, SQLite, SQL Server)
```
### Example 3: Test File Change
```bash
# Changed: src/lib/data/sql-import/dialect-importers/postgresql/__tests__/test-types.test.ts
# Runs: That specific test file
```
## Troubleshooting
1. **Tests not running**: Check if `.husky/.skip-tests` exists
2. **Wrong tests running**: Check `test-mapping.json` patterns
3. **All tests running**: You may have exceeded the change threshold

View File

@@ -1,2 +1,13 @@
#!/bin/sh
# Run linting first
npm run lint || { echo "lint failed, please run \"npm run lint:fix\" to fix the errors." ; exit 1; }
# Check if tests should be skipped
if [ -f .husky/.skip-tests ]; then
echo "⚠️ Tests skipped (remove .husky/.skip-tests to enable)"
exit 0
fi
# Run smart test runner for SQL import related changes
.husky/smart-test-runner.sh || exit 1

214
.husky/smart-test-runner.sh Executable file
View File

@@ -0,0 +1,214 @@
#!/usr/bin/env bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Get the directory of this script
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
CONFIG_FILE="$SCRIPT_DIR/test-mapping.json"
# Get list of staged files
STAGED_FILES=$(git diff --cached --name-only)
# Check if only documentation files are staged
DOC_ONLY=true
NON_DOC_COUNT=0
while IFS= read -r file; do
[ -z "$file" ] && continue
if [[ ! "$file" =~ \.(md|txt|rst)$ ]]; then
DOC_ONLY=false
((NON_DOC_COUNT++))
fi
done <<< "$STAGED_FILES"
# Skip tests if only docs are changed
if [ "$DOC_ONLY" = "true" ]; then
echo -e "${YELLOW} Only documentation files changed, skipping tests.${NC}"
exit 0
fi
# Initialize test tracking
TESTS_TO_RUN=""
MATCHED_FILES=()
# Function to add test path
add_test() {
local test_path=$1
if [ -d "$test_path" ] || [ -f "$test_path" ]; then
# Add to list if not already present
if [[ ! "$TESTS_TO_RUN" =~ "$test_path" ]]; then
if [ -z "$TESTS_TO_RUN" ]; then
TESTS_TO_RUN="$test_path"
else
TESTS_TO_RUN="$TESTS_TO_RUN $test_path"
fi
fi
fi
}
# Function to check if file matches pattern (simple glob matching)
matches_pattern() {
local file=$1
local pattern=$2
# Use bash pattern matching
case "$file" in
$pattern) return 0 ;;
*) return 1 ;;
esac
}
# Always verbose by default
VERBOSE=true
# Process files based on available tools
if command -v jq &> /dev/null && [ -f "$CONFIG_FILE" ]; then
echo -e "${YELLOW}Using configuration from test-mapping.json${NC}"
# Process each staged file
while IFS= read -r file; do
[ -z "$file" ] && continue
# Check against each mapping rule
jq -c '.mappings[]' "$CONFIG_FILE" 2>/dev/null | while read -r mapping; do
name=$(echo "$mapping" | jq -r '.name')
# Check patterns
echo "$mapping" | jq -r '.patterns[]' | while read -r pattern; do
if matches_pattern "$file" "$pattern"; then
# Check exclusions
excluded=false
echo "$mapping" | jq -r '.excludePatterns[]?' 2>/dev/null | while read -r exclude; do
if matches_pattern "$file" "$exclude"; then
excluded=true
break
fi
done
if [ "$excluded" = "false" ]; then
[ "$VERBOSE" = "true" ] && echo -e "${GREEN}✓ Matched rule '$name' for file: $file${NC}"
MATCHED_FILES+=("$file")
# Add tests for this mapping
echo "$mapping" | jq -r '.tests[]' | while read -r test_path; do
[ -n "$test_path" ] && echo "$test_path" >> /tmp/test_paths_$$
done
fi
break
fi
done
done
done <<< "$STAGED_FILES"
# Read test paths from temp file
if [ -f /tmp/test_paths_$$ ]; then
while read -r test_path; do
add_test "$test_path"
done < /tmp/test_paths_$$
rm -f /tmp/test_paths_$$
fi
else
echo -e "${YELLOW}Using built-in patterns (install jq for config file support)${NC}"
# Fallback to hardcoded patterns
while IFS= read -r file; do
[ -z "$file" ] && continue
case "$file" in
# PostgreSQL import files
src/lib/data/sql-import/dialect-importers/postgresql/*.ts)
if [[ ! "$file" =~ \.test\.ts$ ]] && [[ ! "$file" =~ \.spec\.ts$ ]]; then
[ "$VERBOSE" = "true" ] && echo "📝 Changed PostgreSQL import file: $file"
MATCHED_FILES+=("$file")
add_test "src/lib/data/sql-import/dialect-importers/postgresql/__tests__"
fi
;;
# MySQL import files
src/lib/data/sql-import/dialect-importers/mysql/*.ts)
if [[ ! "$file" =~ \.test\.ts$ ]] && [[ ! "$file" =~ \.spec\.ts$ ]]; then
[ "$VERBOSE" = "true" ] && echo "📝 Changed MySQL import file: $file"
MATCHED_FILES+=("$file")
add_test "src/lib/data/sql-import/dialect-importers/mysql/__tests__"
fi
;;
# SQLite import files
src/lib/data/sql-import/dialect-importers/sqlite/*.ts)
if [[ ! "$file" =~ \.test\.ts$ ]] && [[ ! "$file" =~ \.spec\.ts$ ]]; then
[ "$VERBOSE" = "true" ] && echo "📝 Changed SQLite import file: $file"
MATCHED_FILES+=("$file")
add_test "src/lib/data/sql-import/dialect-importers/sqlite/__tests__"
fi
;;
# SQL Server import files
src/lib/data/sql-import/dialect-importers/sql-server/*.ts)
if [[ ! "$file" =~ \.test\.ts$ ]] && [[ ! "$file" =~ \.spec\.ts$ ]]; then
[ "$VERBOSE" = "true" ] && echo "📝 Changed SQL Server import file: $file"
MATCHED_FILES+=("$file")
add_test "src/lib/data/sql-import/dialect-importers/sql-server/__tests__"
fi
;;
# Common SQL import files
src/lib/data/sql-import/*.ts)
if [[ ! "$file" =~ \.test\.ts$ ]] && [[ ! "$file" =~ \.spec\.ts$ ]] && [[ ! "$file" =~ /dialect-importers/ ]]; then
[ "$VERBOSE" = "true" ] && echo "📝 Changed common SQL import file: $file"
MATCHED_FILES+=("$file")
# Run all dialect tests if common files change
add_test "src/lib/data/sql-import/dialect-importers/postgresql/__tests__"
add_test "src/lib/data/sql-import/dialect-importers/mysql/__tests__"
add_test "src/lib/data/sql-import/dialect-importers/sqlite/__tests__"
add_test "src/lib/data/sql-import/dialect-importers/sql-server/__tests__"
fi
;;
# SQL validator
src/lib/data/sql-import/sql-validator.ts)
[ "$VERBOSE" = "true" ] && echo "📝 Changed SQL validator"
MATCHED_FILES+=("$file")
add_test "src/lib/data/sql-import/dialect-importers/postgresql/__tests__"
;;
# Test files themselves
src/lib/data/sql-import/**/*.test.ts|src/lib/data/sql-import/**/*.spec.ts)
[ "$VERBOSE" = "true" ] && echo "📝 Changed test file: $file"
MATCHED_FILES+=("$file")
add_test "$file"
;;
esac
done <<< "$STAGED_FILES"
fi
# Run tests if any were found
if [ -n "$TESTS_TO_RUN" ]; then
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}🧪 Running SQL import tests...${NC}"
[ "$VERBOSE" = "true" ] && echo -e "Matched files: ${#MATCHED_FILES[@]}"
[ "$VERBOSE" = "true" ] && echo -e "Test paths: $TESTS_TO_RUN"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# Run the tests
npm test -- $TESTS_TO_RUN --run
TEST_RESULT=$?
if [ $TEST_RESULT -ne 0 ]; then
echo ""
echo -e "${RED}❌ SQL import tests failed! Please fix the tests before committing.${NC}"
exit 1
else
echo ""
echo -e "${GREEN}✅ SQL import tests passed!${NC}"
fi
else
echo -e "${YELLOW} No SQL import related changes detected, skipping SQL import tests.${NC}"
fi
exit 0

95
.husky/test-mapping.json Normal file
View File

@@ -0,0 +1,95 @@
{
"mappings": [
{
"name": "PostgreSQL Import",
"patterns": [
"src/lib/data/sql-import/dialect-importers/postgresql/*.ts"
],
"excludePatterns": [
"*.test.ts",
"*.spec.ts"
],
"tests": [
"src/lib/data/sql-import/dialect-importers/postgresql/__tests__"
]
},
{
"name": "MySQL Import",
"patterns": [
"src/lib/data/sql-import/dialect-importers/mysql/*.ts"
],
"excludePatterns": [
"*.test.ts",
"*.spec.ts"
],
"tests": [
"src/lib/data/sql-import/dialect-importers/mysql/__tests__"
]
},
{
"name": "SQLite Import",
"patterns": [
"src/lib/data/sql-import/dialect-importers/sqlite/*.ts"
],
"excludePatterns": [
"*.test.ts",
"*.spec.ts"
],
"tests": [
"src/lib/data/sql-import/dialect-importers/sqlite/__tests__"
]
},
{
"name": "SQL Server Import",
"patterns": [
"src/lib/data/sql-import/dialect-importers/sql-server/*.ts"
],
"excludePatterns": [
"*.test.ts",
"*.spec.ts"
],
"tests": [
"src/lib/data/sql-import/dialect-importers/sql-server/__tests__"
]
},
{
"name": "Common SQL Import",
"patterns": [
"src/lib/data/sql-import/*.ts",
"src/lib/data/sql-import/common/*.ts"
],
"excludePatterns": [
"*.test.ts",
"*.spec.ts",
"*/dialect-importers/*"
],
"tests": [
"src/lib/data/sql-import/dialect-importers/postgresql/__tests__",
"src/lib/data/sql-import/dialect-importers/mysql/__tests__",
"src/lib/data/sql-import/dialect-importers/sqlite/__tests__",
"src/lib/data/sql-import/dialect-importers/sql-server/__tests__"
]
},
{
"name": "SQL Validator",
"patterns": [
"src/lib/data/sql-import/sql-validator.ts"
],
"tests": [
"src/lib/data/sql-import/dialect-importers/postgresql/__tests__"
]
},
{
"name": "Import Dialog",
"patterns": [
"src/dialogs/common/import-database/*.tsx",
"src/dialogs/common/import-database/*.ts"
],
"excludePatterns": [
"*.test.tsx",
"*.spec.tsx"
],
"tests": []
}
]
}

2
.nvmrc
View File

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

View File

@@ -1,166 +1,5 @@
# Changelog
## [1.16.1](https://github.com/chartdb/chartdb/compare/v1.16.0...v1.16.1) (2025-09-28)
### Bug Fixes
* add auto-increment field detection in smart-query import ([#935](https://github.com/chartdb/chartdb/issues/935)) ([57b3b87](https://github.com/chartdb/chartdb/commit/57b3b8777fd0a445abf0ba6603faab612d469d5c))
* add rels export dbml ([#937](https://github.com/chartdb/chartdb/issues/937)) ([c3c646b](https://github.com/chartdb/chartdb/commit/c3c646bf7cbb1328f4b2eb85c9a7e929f0fcd3b9))
* dbml diff fields types preview ([#934](https://github.com/chartdb/chartdb/issues/934)) ([bb03309](https://github.com/chartdb/chartdb/commit/bb033091b1f64b888822be1423a80f16f5314f6b))
## [1.16.0](https://github.com/chartdb/chartdb/compare/v1.15.1...v1.16.0) (2025-09-24)
### Features
* add area context menu and UI improvements ([#918](https://github.com/chartdb/chartdb/issues/918)) ([d09379e](https://github.com/chartdb/chartdb/commit/d09379e8be0fa3c83ca77ff62ae815fe4db9869b))
* add quick table mode on canvas ([#915](https://github.com/chartdb/chartdb/issues/915)) ([8954d89](https://github.com/chartdb/chartdb/commit/8954d893bbfee45bb311380115fb14ebbf3a3133))
* add zoom navigation buttons to canvas filter for tables and areas ([#903](https://github.com/chartdb/chartdb/issues/903)) ([a0fb1ed](https://github.com/chartdb/chartdb/commit/a0fb1ed08ba18b66354fa3498d610097a83d4afc))
* **import-db:** add DBML syntax to import database dialog ([#768](https://github.com/chartdb/chartdb/issues/768)) ([af3638d](https://github.com/chartdb/chartdb/commit/af3638da7a9b70f281ceaddbc2f712a713d90cda))
### Bug Fixes
* add areas width and height + table width to diff check ([#931](https://github.com/chartdb/chartdb/issues/931)) ([98f6edd](https://github.com/chartdb/chartdb/commit/98f6edd5c8a8e9130e892b2d841744e0cf63a7bf))
* add diff x,y ([#928](https://github.com/chartdb/chartdb/issues/928)) ([e4c4a3b](https://github.com/chartdb/chartdb/commit/e4c4a3b35484d9ece955a5aec577603dde73d634))
* add support for ALTER TABLE ADD COLUMN in PostgreSQL importer ([#892](https://github.com/chartdb/chartdb/issues/892)) ([ec6e46f](https://github.com/chartdb/chartdb/commit/ec6e46fe81ea1806c179c50a4c5779d8596008aa))
* add tests for diff ([#930](https://github.com/chartdb/chartdb/issues/930)) ([47a7a73](https://github.com/chartdb/chartdb/commit/47a7a73a137b87dfa6e67aff5f939cf64ccf4601))
* dbml edit mode glitch ([#925](https://github.com/chartdb/chartdb/issues/925)) ([93d72a8](https://github.com/chartdb/chartdb/commit/93d72a896bab9aa79d8ea2f876126887e432214c))
* dbml export default time bug ([#922](https://github.com/chartdb/chartdb/issues/922)) ([bc82f9d](https://github.com/chartdb/chartdb/commit/bc82f9d6a8fe4de2f7e0fc465e0a20c5dbf8f41d))
* dbml export renaming fields bug ([#921](https://github.com/chartdb/chartdb/issues/921)) ([26dc299](https://github.com/chartdb/chartdb/commit/26dc299cd28e9890d191c13f84a15ac38ae48b11))
* **dbml:** export array fields without quotes ([#911](https://github.com/chartdb/chartdb/issues/911)) ([5e81c18](https://github.com/chartdb/chartdb/commit/5e81c1848aaa911990e1e881d62525f5254d6d34))
* diff logic ([#927](https://github.com/chartdb/chartdb/issues/927)) ([1b8d51b](https://github.com/chartdb/chartdb/commit/1b8d51b73c4ed4b7c5929adcb17a44927c7defca))
* export dbml issues after upgrade version ([#883](https://github.com/chartdb/chartdb/issues/883)) ([07937a2](https://github.com/chartdb/chartdb/commit/07937a2f51708b1c10b45c2bd1f9a9acf5c3f708))
* export sql + import metadata lib ([#902](https://github.com/chartdb/chartdb/issues/902)) ([ffddcdc](https://github.com/chartdb/chartdb/commit/ffddcdcc987bacb0e0d7e8dea27d08d3a8c5a8c8))
* handle bidirectional relationships in DBML export ([#924](https://github.com/chartdb/chartdb/issues/924)) ([9991077](https://github.com/chartdb/chartdb/commit/99910779789a9c6ef113d06bc3de31e35b9b04d1))
* import dbml set pk field unique ([#920](https://github.com/chartdb/chartdb/issues/920)) ([d6ba4a4](https://github.com/chartdb/chartdb/commit/d6ba4a40749d85d2703f120600df4345dab3c561))
* improve SQL default value parsing for PostgreSQL, MySQL, and SQL Server with proper type handling and casting support ([#900](https://github.com/chartdb/chartdb/issues/900)) ([fe9ef27](https://github.com/chartdb/chartdb/commit/fe9ef275b8619dcfd7e57541a62a6237a16d29a8))
* move area utils ([#932](https://github.com/chartdb/chartdb/issues/932)) ([2dc1a6f](https://github.com/chartdb/chartdb/commit/2dc1a6fc7519e0a455b0e1306601195deb156c96))
* move auto arrange to toolbar ([#904](https://github.com/chartdb/chartdb/issues/904)) ([b016a70](https://github.com/chartdb/chartdb/commit/b016a70691bc22af5720b4de683e8c9353994fcc))
* remove general db creation ([#901](https://github.com/chartdb/chartdb/issues/901)) ([df89f0b](https://github.com/chartdb/chartdb/commit/df89f0b6b9ba3fcc8b05bae4f60c0dc4ad1d2215))
* remove many to many rel option ([#933](https://github.com/chartdb/chartdb/issues/933)) ([c567c0a](https://github.com/chartdb/chartdb/commit/c567c0a5f39157b2c430e92192b6750304d7a834))
* reset increment and default when change field ([#896](https://github.com/chartdb/chartdb/issues/896)) ([e5e1d59](https://github.com/chartdb/chartdb/commit/e5e1d5932762422ea63acfd6cf9fe4f03aa822f7))
* **sql-import:** handle SQL Server DDL with multiple tables, inline foreign keys, and case-insensitive field matching ([#897](https://github.com/chartdb/chartdb/issues/897)) ([2a64dee](https://github.com/chartdb/chartdb/commit/2a64deebb87a11ee3892024c3273d682bb86f7ef))
* **sql-import:** support ALTER TABLE ALTER COLUMN TYPE in PostgreSQL importer ([#895](https://github.com/chartdb/chartdb/issues/895)) ([aa29061](https://github.com/chartdb/chartdb/commit/aa290615caf806d7d0374c848d50b4636fde7e96))
* **sqlite:** improve parser to handle tables without column types and fix column detection ([#914](https://github.com/chartdb/chartdb/issues/914)) ([d3dbf41](https://github.com/chartdb/chartdb/commit/d3dbf41894d74f0ffce9afe3bd810f065aa53017))
* trigger edit table on canvas from context menu ([#919](https://github.com/chartdb/chartdb/issues/919)) ([bdc41c0](https://github.com/chartdb/chartdb/commit/bdc41c0b74d9d9918e7b6cd2152fa07c0c58ce60))
* update deps vulns ([#909](https://github.com/chartdb/chartdb/issues/909)) ([2bd9ca2](https://github.com/chartdb/chartdb/commit/2bd9ca25b2c7b1f053ff4fdc8c5cfc1b0e65901d))
* upgrade dbml lib ([#880](https://github.com/chartdb/chartdb/issues/880)) ([d8e0bc7](https://github.com/chartdb/chartdb/commit/d8e0bc7db8881971ddaea7177bcebee13cc865f6))
## [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)

View File

@@ -4,9 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, max-image-preview:large" />
<meta name="robots" content="max-image-preview:large" />
<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.gstatic.com" crossorigin />
<link
@@ -16,19 +15,14 @@
<script src="/config.js"></script>
<script>
// Load analytics only if not disabled
(function () {
const disableAnalytics =
(window.env && window.env.DISABLE_ANALYTICS === 'true') ||
(typeof process !== 'undefined' &&
process.env &&
process.env.VITE_DISABLE_ANALYTICS === 'true');
(function() {
const disableAnalytics = (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);
}

983
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "chartdb",
"private": true,
"version": "1.16.1",
"version": "1.13.2",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,13 +11,12 @@
"preview": "vite preview",
"prepare": "husky",
"test": "vitest",
"test:ci": "vitest run --reporter=verbose --bail=1",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@dbml/core": "^3.13.9",
"@dbml/core": "^3.9.5",
"@dnd-kit/sortable": "^8.0.0",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.0",
@@ -26,24 +25,24 @@
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.2.7",
"@radix-ui/react-tooltip": "^1.1.8",
"@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.8.2",
"@xyflow/react": "^12.3.1",
"ahooks": "^3.8.1",
"ai": "^3.3.14",
"class-variance-authority": "^0.7.1",
@@ -54,9 +53,8 @@
"html-to-image": "^1.11.11",
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.525.0",
"lucide-react": "^0.441.0",
"monaco-editor": "^0.52.0",
"motion": "^12.23.6",
"nanoid": "^5.0.7",
"node-sql-parser": "^5.3.2",
"react": "^18.3.1",

View File

@@ -0,0 +1,166 @@
# PostgreSQL vs MySQL Parser Comparison Analysis
## Overview
This document compares how the PostgreSQL and MySQL parsers in ChartDB handle SQL parsing, focusing on the differences that could cause the same SQL file to produce different results.
## 1. SQL Sanitization and Comment Handling
### PostgreSQL Parser (`postgresql-improved.ts`)
#### Comment Removal Strategy:
1. **Order**: Comments are removed FIRST, before any other processing
2. **Multi-line comments**: Removed using regex: `/\/\*[\s\S]*?\*\//g`
3. **Single-line comments**: Removed line-by-line, checking for `--` while respecting string boundaries
4. **String-aware**: Preserves `--` inside quoted strings
```typescript
// PostgreSQL approach (lines 60-100)
// 1. First removes ALL multi-line comments
cleanedSQL = cleanedSQL.replace(/\/\*[\s\S]*?\*\//g, '');
// 2. Then processes single-line comments while respecting strings
for (let i = 0; i < line.length; i++) {
// Tracks if we're inside a string to avoid removing -- inside quotes
}
```
### MySQL Parser (`mysql-improved.ts`)
#### Comment Removal Strategy:
1. **Order**: Comments are sanitized but with special handling for problematic patterns
2. **Special handling**: Specifically fixes multi-line comments that contain quotes or JSON
3. **Line-by-line**: Processes comments line by line, removing lines that start with `--` or `#`
```typescript
// MySQL approach (lines 35-67)
// 1. First fixes specific problematic patterns
result = result.replace(/--\s*"[^"]*",?\s*\n\s*"[^"]*".*$/gm, function(match) {
return match.replace(/\n/g, ' ');
});
// 2. Then removes comment lines entirely
.map((line) => {
if (trimmed.startsWith('--') || trimmed.startsWith('#')) {
return '';
}
return line;
})
```
**Key Difference**: PostgreSQL removes ALL comments upfront, while MySQL tries to fix problematic comment patterns first, then removes comment lines.
## 2. Order of Operations
### PostgreSQL Parser
1. **Preprocess SQL** (removes all comments first)
2. **Split statements** by semicolons (handles dollar quotes)
3. **Categorize statements** (table, index, alter, etc.)
4. **Parse with node-sql-parser**
5. **Fallback to regex** if parser fails
6. **Extract relationships**
### MySQL Parser
1. **Validate syntax** (checks for known issues)
2. **Sanitize SQL** (fixes problematic patterns)
3. **Extract statements** by semicolons
4. **Parse with node-sql-parser**
5. **Fallback to regex** if parser fails
6. **Process relationships**
**Key Difference**: MySQL validates BEFORE sanitizing, while PostgreSQL sanitizes first. This means MySQL can detect and report issues that PostgreSQL might silently fix.
## 3. Multi-line Comment Handling
### PostgreSQL
- Removes ALL multi-line comments using `[\s\S]*?` pattern
- No special handling for comments containing quotes or JSON
- Clean removal before any parsing
### MySQL
- Specifically detects and fixes multi-line comments with quotes:
```sql
-- "Beliebt",
"Empfohlen" -- This breaks MySQL parser
```
- Detects JSON arrays in comments spanning lines:
```sql
-- [
"Ubuntu 22.04",
"CentOS 8"
] -- This also breaks MySQL parser
```
- Converts these to single-line comments before parsing
**Key Difference**: MySQL has specific handling for problematic comment patterns that PostgreSQL simply removes entirely.
## 4. Statement Splitting
### PostgreSQL
- Handles PostgreSQL-specific dollar quotes (`$$ ... $$`)
- Tracks quote depth for proper splitting
- Supports function bodies with dollar quotes
### MySQL
- Simple quote tracking (single, double, backtick)
- Handles escape sequences (`\`)
- No special quote constructs
## 5. Validation Approach
### PostgreSQL
- No pre-validation
- Relies on parser and fallback regex
- Reports warnings for unsupported features
### MySQL
- Pre-validates SQL before parsing
- Detects known problematic patterns:
- Multi-line comments with quotes
- JSON arrays in comments
- Inline REFERENCES (PostgreSQL syntax)
- Missing semicolons
- Can reject SQL before attempting to parse
## 6. Why Same SQL Gives Different Results
### Example Problematic SQL:
```sql
CREATE TABLE products (
id INT PRIMARY KEY,
status VARCHAR(50), -- "active",
"inactive", "pending"
data JSON -- [
{"key": "value"},
{"key": "value2"}
]
);
```
### PostgreSQL Result:
- Successfully parses (comments are removed entirely)
- Table created with proper columns
### MySQL Result:
- Validation fails with errors:
- MULTILINE_COMMENT_QUOTE at line 3
- MULTILINE_JSON_COMMENT at line 5
- Import blocked unless validation is skipped
## 7. Recommendations
1. **For Cross-Database Compatibility**:
- Avoid multi-line comments with quotes or JSON
- Keep comments on single lines
- Use proper FOREIGN KEY syntax instead of inline REFERENCES
2. **For MySQL Import**:
- Fix validation errors before import
- Or use `skipValidation: true` option if SQL is known to work
3. **For PostgreSQL Import**:
- Be aware that comments are stripped entirely
- Complex comments might hide syntax issues
## Conclusion
The main difference is that PostgreSQL takes a "remove all comments first" approach, while MySQL tries to detect and handle problematic comment patterns. This makes PostgreSQL more forgiving but MySQL more explicit about potential issues. The same SQL file can succeed in PostgreSQL but fail in MySQL if it contains multi-line comments with special characters.

View File

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

View File

@@ -1,7 +1,7 @@
import { cva } from 'class-variance-authority';
export const buttonVariants = cva(
'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',
'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',
{
variants: {
variant: {

View File

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

View File

@@ -1,51 +0,0 @@
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

@@ -9,14 +9,12 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'comment', foreground: '6A9955' }, // Comments
{ token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords
{ token: 'string', foreground: 'CE9178' }, // Strings
{ token: 'annotation', foreground: '9CDCFE' }, // [annotations]
{ token: 'delimiter', foreground: 'D4D4D4' }, // Braces {}
{ token: 'operator', foreground: 'D4D4D4' }, // Operators
{ token: 'type', foreground: '4EC9B0' }, // Data types
{ token: 'identifier', foreground: '9CDCFE' }, // Field names
{ token: 'datatype', foreground: '4EC9B0' }, // Data types
],
colors: {},
});
@@ -25,14 +23,12 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
base: 'vs',
inherit: true,
rules: [
{ token: 'comment', foreground: '008000' }, // Comments
{ token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords
{ token: 'string', foreground: 'A31515' }, // Strings
{ token: 'annotation', foreground: '001080' }, // [annotations]
{ token: 'delimiter', foreground: '000000' }, // Braces {}
{ token: 'operator', foreground: '000000' }, // Operators
{ token: 'type', foreground: '267F99' }, // Data types
{ token: 'identifier', foreground: '001080' }, // Field names
],
colors: {},
});
@@ -41,63 +37,17 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
const datatypePattern = dataTypesNames.join('|');
monaco.languages.setMonarchTokensProvider('dbml', {
keywords: ['Table', 'Ref', 'Indexes', 'Note', 'Enum', 'enum'],
keywords: ['Table', 'Ref', 'Indexes'],
datatypes: dataTypesNames,
operators: ['>', '<', '-'],
tokenizer: {
root: [
// Comments
[/\/\/.*$/, 'comment'],
// Keywords - case insensitive
[
/\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',
],
// Annotations in brackets
[/\b(Table|Ref|Indexes)\b/, 'keyword'],
[/\[.*?\]/, 'annotation'],
// Strings
[/'''/, 'string', '@tripleQuoteString'],
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-terminated string
[/'([^'\\]|\\.)*$/, 'string.invalid'], // non-terminated string
[/"/, 'string', '@string_double'],
[/'/, 'string', '@string_single'],
[/`.*?`/, 'string'],
// Delimiters and operators
[/[{}()]/, 'delimiter'],
[/[<>-]/, 'operator'],
[/:/, 'delimiter'],
// Data types
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'],
// Numbers
[/\d+/, 'number'],
// Identifiers
[/[a-zA-Z_]\w*/, 'identifier'],
],
string_double: [
[/[^\\"]+/, 'string'],
[/\\./, 'string.escape'],
[/"/, 'string', '@pop'],
],
string_single: [
[/[^\\']+/, 'string'],
[/\\./, 'string.escape'],
[/'/, 'string', '@pop'],
],
tripleQuoteString: [
[/[^']+/, 'string'],
[/'''/, 'string', '@pop'],
[/'/, 'string'],
[/".*?"/, 'string'],
[/'.*?'/, 'string'],
[/[{}]/, 'delimiter'],
[/[<>]/, 'operator'],
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching
],
},
});

View File

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

View File

@@ -4,7 +4,6 @@ import { Cross2Icon } from '@radix-ui/react-icons';
import { cn } from '@/lib/utils';
import { ScrollArea } from '../scroll-area/scroll-area';
import { ChevronLeft } from 'lucide-react';
const Dialog = DialogPrimitive.Root;
@@ -33,75 +32,28 @@ const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
showClose?: boolean;
showBack?: boolean;
backButtonClassName?: string;
blurBackground?: boolean;
forceOverlay?: boolean;
onBackClick?: () => void;
}
>(
(
{
className,
children,
showClose,
showBack,
onBackClick,
backButtonClassName,
blurBackground,
forceOverlay,
...props
},
ref
) => (
<DialogPortal>
{forceOverlay ? (
<div
className={cn(
'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>
)
);
>(({ className, children, showClose, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<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}
{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;
const DialogHeader = ({

View File

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

View File

@@ -2,13 +2,16 @@ import React from 'react';
import { cn } from '@/lib/utils';
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'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',
'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',
className
)}
ref={ref}

View File

@@ -1,121 +0,0 @@
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,7 +27,6 @@ export interface SelectBoxOption {
regex?: string;
extractRegex?: RegExp;
group?: string;
icon?: React.ReactNode;
}
export interface SelectBoxProps {
@@ -54,10 +53,6 @@ export interface SelectBoxProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
popoverClassName?: string;
readonly?: boolean;
footerButtons?: React.ReactNode;
commandOnMouseDown?: (e: React.MouseEvent) => void;
commandOnClick?: (e: React.MouseEvent) => void;
}
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
@@ -83,10 +78,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
open,
onOpenChange: setOpen,
popoverClassName,
readonly,
footerButtons,
commandOnMouseDown,
commandOnClick,
},
ref
) => {
@@ -102,12 +93,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
(isOpen: boolean) => {
setOpen?.(isOpen);
setIsOpen(isOpen);
if (isOpen) {
setSearchTerm('');
}
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
},
[setOpen]
);
@@ -161,20 +146,18 @@ 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' : ''}`}
>
<span>{option.label}</span>
{!readonly ? (
<span
onClick={(e) => {
e.preventDefault();
handleSelect(option.value);
}}
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
>
<Cross2Icon />
</span>
) : null}
<span
onClick={(e) => {
e.preventDefault();
handleSelect(option.value);
}}
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
>
<Cross2Icon />
</span>
</span>
)),
[options, value, handleSelect, oneLine, keepOrder, readonly]
[options, value, handleSelect, oneLine, keepOrder]
);
const isAllSelected = React.useMemo(
@@ -244,11 +227,9 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
onSelect={() =>
handleSelect(
option.value,
matches?.map((match) => match?.toString())
matches?.map((match) => match.toString())
)
}
onMouseDown={commandOnMouseDown}
onClick={commandOnClick}
>
{multiple && (
<div
@@ -263,11 +244,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</div>
)}
<div className="flex flex-1 items-center truncate">
{option.icon ? (
<span className="mr-2 shrink-0">
{option.icon}
</span>
) : null}
<span>
{isRegexMatch ? searchTerm : option.label}
{!isRegexMatch && optionSuffix
@@ -294,15 +270,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</CommandItem>
);
},
[
value,
multiple,
searchTerm,
handleSelect,
optionSuffix,
commandOnClick,
commandOnMouseDown,
]
[value, multiple, searchTerm, handleSelect, optionSuffix]
);
return (
@@ -310,7 +278,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
<div
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' : ''} ${readonly ? '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' : ''}`,
className
)}
>
@@ -380,8 +348,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
popoverClassName
)}
align="center"
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
<Command
filter={(value, search, keywords) => {
@@ -471,9 +437,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
</div>
</ScrollArea>
</Command>
{footerButtons ? (
<div className="border-t">{footerButtons}</div>
) : null}
</PopoverContent>
</Popover>
);

View File

@@ -29,7 +29,6 @@ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = '16rem';
const SIDEBAR_WIDTH_MOBILE = '18rem';
const SIDEBAR_WIDTH_ICON = '3rem';
const SIDEBAR_WIDTH_ICON_EXTENDED = '4rem';
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
type SidebarContext = {
@@ -143,8 +142,6 @@ const SidebarProvider = React.forwardRef<
{
'--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
'--sidebar-width-icon-extended':
SIDEBAR_WIDTH_ICON_EXTENDED,
...style,
} as React.CSSProperties
}
@@ -169,7 +166,7 @@ const Sidebar = React.forwardRef<
React.ComponentProps<'div'> & {
side?: 'left' | 'right';
variant?: 'sidebar' | 'floating' | 'inset';
collapsible?: 'offcanvas' | 'icon' | 'icon-extended' | 'none';
collapsible?: 'offcanvas' | 'icon' | 'none';
}
>(
(
@@ -248,8 +245,8 @@ const Sidebar = React.forwardRef<
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
? '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-extended]:w-[--sidebar-width-icon-extended]'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
)}
/>
<div
@@ -260,8 +257,8 @@ const Sidebar = React.forwardRef<
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset'
? '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-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended] group-data-[side=left]:border-r group-data-[side=right]:border-l',
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
className
)}
{...props}
@@ -424,7 +421,7 @@ const SidebarContent = React.forwardRef<
ref={ref}
data-sidebar="content"
className={cn(
'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',
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
className
)}
{...props}
@@ -464,7 +461,6 @@ const SidebarGroupLabel = React.forwardRef<
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',
'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
)}
{...props}
@@ -487,7 +483,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',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
'group-data-[collapsible=icon]:hidden',
className
)}
{...props}
@@ -536,7 +532,7 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = 'SidebarMenuItem';
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-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',
'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',
{
variants: {
variant: {
@@ -640,7 +636,7 @@ const SidebarMenuAction = React.forwardRef<
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
'group-data-[collapsible=icon]:hidden',
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',
className
@@ -757,7 +753,7 @@ const SidebarMenuSubButton = React.forwardRef<
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
'group-data-[collapsible=icon]:hidden',
className
)}
{...props}

View File

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

View File

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

View File

@@ -13,17 +13,15 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
// <TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
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 origin-[--radix-tooltip-content-transform-origin]',
'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',
className
)}
{...props}
/>
// </TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

View File

@@ -1,17 +0,0 @@
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

@@ -1,461 +0,0 @@
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

@@ -1,41 +0,0 @@
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

@@ -1,153 +0,0 @@
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,18 +12,6 @@ export interface CanvasContext {
}) => void;
setOverlapGraph: (graph: Graph<string>) => void;
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>({
@@ -31,8 +19,4 @@ export const canvasContext = createContext<CanvasContext>({
fitView: emptyFn,
setOverlapGraph: emptyFn,
overlapGraph: createGraph(),
setShowFilter: emptyFn,
showFilter: false,
editTableModeTable: null,
setEditTableModeTable: emptyFn,
});

View File

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

View File

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

View File

@@ -1,15 +1,12 @@
import React, { useCallback, useMemo, useState } from 'react';
import type { DBTable } from '@/lib/domain/db-table';
import { deepCopy, generateId } from '@/lib/utils';
import { defaultTableColor, defaultAreaColor, viewColor } from '@/lib/colors';
import { randomColor } from '@/lib/colors';
import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
import { chartDBContext } from './chartdb-context';
import { DatabaseType } from '@/lib/domain/database-type';
import type { DBField } from '@/lib/domain/db-field';
import {
getTableIndexesWithPrimaryKey,
type DBIndex,
} from '@/lib/domain/db-index';
import type { DBIndex } from '@/lib/domain/db-index';
import type { DBRelationship } from '@/lib/domain/db-relationship';
import { useStorage } from '@/hooks/use-storage';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
@@ -20,6 +17,7 @@ import {
databasesWithSchemas,
schemaNameToSchemaId,
} from '@/lib/domain/db-schema';
import { useLocalConfig } from '@/hooks/use-local-config';
import { defaultSchemas } from '@/lib/data/default-schemas';
import { useEventEmitter } from 'ahooks';
import type { DBDependency } from '@/lib/domain/db-dependency';
@@ -41,11 +39,11 @@ export const ChartDBProvider: React.FC<
React.PropsWithChildren<ChartDBProviderProps>
> = ({ children, diagram, readonly: readonlyProp }) => {
const { hasDiff } = useDiff();
const storageDB = useStorage();
let db = useStorage();
const events = useEventEmitter<ChartDBEvent>();
const { setSchemasFilter, schemasFilter } = useLocalConfig();
const { addUndoAction, resetRedoStack, resetUndoStack } =
useRedoUndoStack();
const [diagramId, setDiagramId] = useState('');
const [diagramName, setDiagramName] = useState('');
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
@@ -67,17 +65,13 @@ export const ChartDBProvider: React.FC<
const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
diagram?.customTypes ?? []
);
const { events: diffEvents } = useDiff();
const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
useState<string>();
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
const { tablesToAdd, fieldsToAdd, relationshipsToAdd } = event.data;
const { tablesAdded, fieldsAdded, relationshipsAdded } = event.data;
setTables((tables) =>
[...tables, ...(tablesToAdd ?? [])].map((table) => {
const fields = fieldsToAdd.get(table.id);
[...tables, ...(tablesAdded ?? [])].map((table) => {
const fields = fieldsAdded.get(table.id);
return fields
? { ...table, fields: [...table.fields, ...fields] }
: table;
@@ -85,22 +79,23 @@ export const ChartDBProvider: React.FC<
);
setRelationships((relationships) => [
...relationships,
...(relationshipsToAdd ?? []),
...(relationshipsAdded ?? []),
]);
}, []);
diffEvents.useSubscription(diffCalculatedHandler);
const defaultSchemaName = useMemo(
() => defaultSchemas[databaseType],
[databaseType]
);
const defaultSchemaName = defaultSchemas[databaseType];
const readonly = useMemo(
() => readonlyProp ?? hasDiff ?? false,
[readonlyProp, hasDiff]
);
if (readonly) {
db = storageInitialValue;
}
const schemas = useMemo(
() =>
databasesWithSchemas.includes(databaseType)
@@ -111,11 +106,9 @@ export const ChartDBProvider: React.FC<
.filter((schema) => !!schema) as string[]
),
]
.sort((a, b) => {
if (a === defaultSchemaName) return -1;
if (b === defaultSchemaName) return 1;
return a.localeCompare(b);
})
.sort((a, b) =>
a === defaultSchemaName ? -1 : a.localeCompare(b)
)
.map(
(schema): DBSchema => ({
id: schemaNameToSchemaId(schema),
@@ -129,11 +122,34 @@ export const ChartDBProvider: React.FC<
[tables, defaultSchemaName, databaseType]
);
const db = useMemo(
() => (readonly ? storageInitialValue : storageDB),
[storageDB, readonly]
const filterSchemas: ChartDBContext['filterSchemas'] = useCallback(
(schemaIds) => {
setSchemasFilter((prev) => ({
...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(
() => ({
id: diagramId,
@@ -345,17 +361,12 @@ export const ChartDBProvider: React.FC<
},
],
indexes: [],
color: attributes?.isView ? viewColor : defaultTableColor,
color: randomColor(),
createdAt: Date.now(),
isView: false,
order: tables.length,
...attributes,
};
table.indexes = getTableIndexesWithPrimaryKey({
table,
});
await addTable(table);
return table;
@@ -647,30 +658,17 @@ export const ChartDBProvider: React.FC<
options = { updateHistory: true }
) => {
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) =>
tables.map((table) => {
if (table.id === tableId) {
return updateTableFn(table);
}
return table;
})
tables.map((table) =>
table.id === tableId
? {
...table,
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
}
: table
)
);
const table = await db.getTable({ diagramId, id: tableId });
@@ -685,7 +683,10 @@ export const ChartDBProvider: React.FC<
db.updateTable({
id: tableId,
attributes: {
...updateTableFn(table),
...table,
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
},
}),
]);
@@ -712,29 +713,19 @@ export const ChartDBProvider: React.FC<
fieldId: string,
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 prevField = getField(tableId, fieldId);
setTables((tables) =>
tables.map((table) => {
if (table.id === tableId) {
return updateTableFn(table);
}
return table;
})
tables.map((table) =>
table.id === tableId
? {
...table,
fields: table.fields.filter(
(f) => f.id !== fieldId
),
}
: table
)
);
events.emit({
@@ -758,7 +749,8 @@ export const ChartDBProvider: React.FC<
db.updateTable({
id: tableId,
attributes: {
...updateTableFn(table),
...table,
fields: table.fields.filter((f) => f.id !== fieldId),
},
}),
]);
@@ -1114,15 +1106,12 @@ export const ChartDBProvider: React.FC<
const sourceFieldName = sourceField?.name ?? '';
const targetTable = getTable(targetTableId);
const targetTableSchema = targetTable?.schema;
const relationship: DBRelationship = {
id: generateId(),
name: `${sourceTableName}_${sourceFieldName}_fk`,
sourceSchema: sourceTable?.schema,
sourceTableId,
targetSchema: targetTableSchema,
targetSchema: sourceTable?.schema,
targetTableId,
sourceFieldId,
targetFieldId,
@@ -1444,7 +1433,7 @@ export const ChartDBProvider: React.FC<
y: 0,
width: 300,
height: 200,
color: defaultAreaColor,
color: randomColor(),
...attributes,
};
@@ -1527,37 +1516,22 @@ export const ChartDBProvider: React.FC<
[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'] =
useCallback(
(diagram) => {
async (diagram) => {
setDiagramId(diagram.id);
setDiagramName(diagram.name);
setDatabaseType(diagram.databaseType);
setDatabaseEdition(diagram.databaseEdition);
setTables(diagram.tables ?? []);
setRelationships(diagram.relationships ?? []);
setDependencies(diagram.dependencies ?? []);
setAreas(diagram.areas ?? []);
setCustomTypes(diagram.customTypes ?? []);
setTables(diagram?.tables ?? []);
setRelationships(diagram?.relationships ?? []);
setDependencies(diagram?.dependencies ?? []);
setAreas(diagram?.areas ?? []);
setCustomTypes(diagram?.customTypes ?? []);
setDiagramCreatedAt(diagram.createdAt);
setDiagramUpdatedAt(diagram.updatedAt);
setHighlightedCustomTypeId(undefined);
events.emit({ action: 'load_diagram', data: { diagram } });
resetRedoStack();
resetUndoStack();
},
[
setDiagramId,
@@ -1571,26 +1545,13 @@ export const ChartDBProvider: React.FC<
setCustomTypes,
setDiagramCreatedAt,
setDiagramUpdatedAt,
setHighlightedCustomTypeId,
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(
async (diagramId: string) => {
const diagram = await storageDB.getDiagram(diagramId, {
const diagram = await db.getDiagram(diagramId, {
includeRelationships: true,
includeTables: true,
includeDependencies: true,
@@ -1604,7 +1565,7 @@ export const ChartDBProvider: React.FC<
return diagram;
},
[storageDB, loadDiagramFromData]
[db, loadDiagramFromData]
);
// Custom type operations
@@ -1763,9 +1724,10 @@ export const ChartDBProvider: React.FC<
areas,
currentDiagram,
schemas,
filteredSchemas,
events,
readonly,
updateDiagramData,
filterSchemas,
updateDiagramId,
updateDiagramName,
loadDiagram,
@@ -1822,8 +1784,6 @@ export const ChartDBProvider: React.FC<
removeCustomType,
removeCustomTypes,
updateCustomType,
highlightCustomTypeId,
highlightedCustomType,
}}
>
{children}

View File

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

View File

@@ -1,50 +0,0 @@
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

@@ -1,559 +0,0 @@
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

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

View File

@@ -7,6 +7,7 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
import type { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
import type { CreateDiagramDialogProps } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
@@ -66,6 +67,12 @@ export interface DialogContext {
params: Omit<ImportDiagramDialogProps, 'dialog'>
) => void;
closeImportDiagramDialog: () => void;
// Import DBML dialog
openImportDBMLDialog: (
params?: Omit<ImportDBMLDialogProps, 'dialog'>
) => void;
closeImportDBMLDialog: () => void;
}
export const dialogContext = createContext<DialogContext>({
@@ -89,4 +96,6 @@ export const dialogContext = createContext<DialogContext>({
closeExportDiagramDialog: emptyFn,
openImportDiagramDialog: emptyFn,
closeImportDiagramDialog: emptyFn,
openImportDBMLDialog: emptyFn,
closeImportDBMLDialog: emptyFn,
});

View File

@@ -20,6 +20,8 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
import { ImportDBMLDialog } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -130,6 +132,11 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
useState(false);
// Import DBML dialog
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
const [importDBMLDialogParams, setImportDBMLDialogParams] =
useState<Omit<ImportDBMLDialogProps, 'dialog'>>();
return (
<dialogContext.Provider
value={{
@@ -158,6 +165,11 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
closeImportDiagramDialog: () =>
setOpenImportDiagramDialog(false),
openImportDBMLDialog: (params) => {
setImportDBMLDialogParams(params);
setOpenImportDBMLDialog(true);
},
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
}}
>
{children}
@@ -192,6 +204,10 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
/>
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
<ImportDBMLDialog
dialog={{ open: openImportDBMLDialog }}
{...importDBMLDialogParams}
/>
</dialogContext.Provider>
);
};

View File

@@ -15,9 +15,9 @@ export type DiffEventBase<T extends DiffEventType, D> = {
};
export type DiffCalculatedData = {
tablesToAdd: DBTable[];
fieldsToAdd: Map<string, DBField[]>;
relationshipsToAdd: DBRelationship[];
tablesAdded: DBTable[];
fieldsAdded: Map<string, DBField[]>;
relationshipsAdded: DBRelationship[];
};
export type DiffCalculatedEvent = DiffEventBase<
@@ -32,33 +32,21 @@ export interface DiffContext {
originalDiagram: Diagram | null;
diffMap: DiffMap;
hasDiff: boolean;
isSummaryOnly: boolean;
calculateDiff: ({
diagram,
newDiagram,
options,
}: {
diagram: Diagram;
newDiagram: Diagram;
options?: {
summaryOnly?: boolean;
};
}) => { foundDiff: boolean };
resetDiff: () => void;
}) => void;
// table diff
checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean;
checkIfNewTable: ({ tableId }: { tableId: string }) => boolean;
checkIfTableRemoved: ({ tableId }: { tableId: string }) => boolean;
getTableNewName: ({ tableId }: { tableId: string }) => {
old: string;
new: string;
} | null;
getTableNewColor: ({ tableId }: { tableId: string }) => {
old: string;
new: string;
} | null;
getTableNewName: ({ tableId }: { tableId: string }) => string | null;
getTableNewColor: ({ tableId }: { tableId: string }) => string | null;
// field diff
checkIfFieldHasChange: ({
@@ -70,41 +58,8 @@ export interface DiffContext {
}) => boolean;
checkIfFieldRemoved: ({ fieldId }: { fieldId: string }) => boolean;
checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean;
getFieldNewName: ({
fieldId,
}: {
fieldId: string;
}) => { old: string; new: string } | null;
getFieldNewType: ({
fieldId,
}: {
fieldId: string;
}) => { old: DataType; new: DataType } | null;
getFieldNewPrimaryKey: ({
fieldId,
}: {
fieldId: string;
}) => { old: boolean; new: boolean } | null;
getFieldNewNullable: ({
fieldId,
}: {
fieldId: string;
}) => { old: boolean; new: boolean } | null;
getFieldNewCharacterMaximumLength: ({
fieldId,
}: {
fieldId: string;
}) => { old: string; new: string } | null;
getFieldNewScale: ({
fieldId,
}: {
fieldId: string;
}) => { old: number; new: number } | null;
getFieldNewPrecision: ({
fieldId,
}: {
fieldId: string;
}) => { old: number; new: number } | null;
getFieldNewName: ({ fieldId }: { fieldId: string }) => string | null;
getFieldNewType: ({ fieldId }: { fieldId: string }) => DataType | null;
// relationship diff
checkIfNewRelationship: ({

View File

@@ -32,11 +32,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const [fieldsChanged, setFieldsChanged] = React.useState<
Map<string, boolean>
>(new Map<string, boolean>());
const [isSummaryOnly, setIsSummaryOnly] = React.useState<boolean>(false);
const events = useEventEmitter<DiffEvent>();
const generateFieldsToAddMap = useCallback(
const generateNewFieldsMap = useCallback(
({
diffMap,
newDiagram,
@@ -66,7 +65,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
[]
);
const findRelationshipsToAdd = useCallback(
const findNewRelationships = useCallback(
({
diffMap,
newDiagram,
@@ -101,7 +100,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
diffMap: DiffMap;
}): DiffCalculatedData => {
return {
tablesToAdd:
tablesAdded:
newDiagram?.tables?.filter((table) => {
const tableKey = getDiffMapKey({
diffObject: 'table',
@@ -114,21 +113,21 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
);
}) ?? [],
fieldsToAdd: generateFieldsToAddMap({
fieldsAdded: generateNewFieldsMap({
diffMap: diffMap,
newDiagram: newDiagram,
}),
relationshipsToAdd: findRelationshipsToAdd({
relationshipsAdded: findNewRelationships({
diffMap: diffMap,
newDiagram: newDiagram,
}),
};
},
[findRelationshipsToAdd, generateFieldsToAddMap]
[findNewRelationships, generateNewFieldsMap]
);
const calculateDiff: DiffContext['calculateDiff'] = useCallback(
({ diagram, newDiagram: newDiagramArg, options }) => {
({ diagram, newDiagram: newDiagramArg }) => {
const {
diffMap: newDiffs,
changedTables: newChangedTables,
@@ -140,7 +139,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
setFieldsChanged(newChangedFields);
setNewDiagram(newDiagramArg);
setOriginalDiagram(diagram);
setIsSummaryOnly(options?.summaryOnly ?? false);
events.emit({
action: 'diff_calculated',
@@ -149,8 +147,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
newDiagram: newDiagramArg,
}),
});
return { foundDiff: !!newDiffs.size };
},
[setDiffMap, events, generateDiffCalculatedData]
);
@@ -167,10 +163,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(tableNameKey);
if (diff?.type === 'changed') {
return {
new: diff.newValue as string,
old: diff.oldValue as string,
};
return diff.newValue as string;
}
}
@@ -191,10 +184,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(tableColorKey);
if (diff?.type === 'changed') {
return {
new: diff.newValue as string,
old: diff.oldValue as string,
};
return diff.newValue as string;
}
}
return null;
@@ -285,10 +275,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return {
old: diff.oldValue as string,
new: diff.newValue as string,
};
return diff.newValue as string;
}
}
@@ -309,136 +296,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
const diff = diffMap.get(fieldKey);
if (diff?.type === 'changed') {
return {
old: diff.oldValue as DataType,
new: diff.newValue as DataType,
};
}
}
return null;
},
[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 {
old: diff.oldValue as boolean,
new: 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 {
old: diff.oldValue as boolean,
new: 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 {
old: diff.oldValue as string,
new: 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 {
old: diff.oldValue as number,
new: 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 {
old: diff.oldValue as number,
new: diff.newValue as number,
};
return diff.newValue as DataType;
}
}
@@ -481,15 +339,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
[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 (
<diffContext.Provider
value={{
@@ -497,10 +346,8 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
originalDiagram,
diffMap,
hasDiff: diffMap.size > 0,
isSummaryOnly,
calculateDiff,
resetDiff,
// table diff
getTableNewName,
@@ -515,11 +362,6 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
checkIfNewField,
getFieldNewName,
getFieldNewType,
getFieldNewPrimaryKey,
getFieldNewNullable,
getFieldNewCharacterMaximumLength,
getFieldNewScale,
getFieldNewPrecision,
// relationship diff
checkIfNewRelationship,

View File

@@ -8,7 +8,6 @@ export enum KeyboardShortcutAction {
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
SHOW_ALL = 'show_all',
TOGGLE_THEME = 'toggle_theme',
TOGGLE_FILTER = 'toggle_filter',
}
export interface KeyboardShortcut {
@@ -72,13 +71,6 @@ export const keyboardShortcuts: Record<
keyCombinationMac: 'meta+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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,6 @@ import type { ChartDBConfig } from '@/lib/domain/config';
import type { DBDependency } from '@/lib/domain/db-dependency';
import type { Area } from '@/lib/domain/area';
import type { DBCustomType } from '@/lib/domain/db-custom-type';
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
children,
@@ -45,10 +44,6 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
ChartDBConfig & { id: number },
'id' // primary key "id" (for the typings only)
>;
diagram_filters: EntityTable<
DiagramFilter & { diagramId: string },
'diagramId' // primary key "id" (for the typings only)
>;
};
// Schema declaration:
@@ -195,27 +190,6 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
config: '++id, defaultDiagramId',
});
dexieDB
.version(12)
.stores({
diagrams:
'++id, name, databaseType, databaseEdition, createdAt, updatedAt',
db_tables:
'++id, diagramId, name, schema, x, y, fields, indexes, color, createdAt, width, comment, isView, isMaterializedView, order',
db_relationships:
'++id, diagramId, name, sourceSchema, sourceTableId, targetSchema, targetTableId, sourceFieldId, targetFieldId, type, createdAt',
db_dependencies:
'++id, diagramId, schema, tableId, dependentSchema, dependentTableId, createdAt',
areas: '++id, diagramId, name, x, y, width, height, color',
db_custom_types:
'++id, diagramId, schema, type, kind, values, fields',
config: '++id, defaultDiagramId',
diagram_filters: 'diagramId, tableIds, schemasIds',
})
.upgrade((tx) => {
tx.table('config').clear();
});
dexieDB.on('ready', async () => {
const config = await dexieDB.config.get(1);
@@ -243,34 +217,6 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
[db]
);
const getDiagramFilter: StorageContext['getDiagramFilter'] = useCallback(
async (diagramId: string): Promise<DiagramFilter | undefined> => {
const filter = await db.diagram_filters.get({ diagramId });
return filter;
},
[db]
);
const updateDiagramFilter: StorageContext['updateDiagramFilter'] =
useCallback(
async (diagramId, filter): Promise<void> => {
await db.diagram_filters.put({
diagramId,
...filter,
});
},
[db]
);
const deleteDiagramFilter: StorageContext['deleteDiagramFilter'] =
useCallback(
async (diagramId: string): Promise<void> => {
await db.diagram_filters.where({ diagramId }).delete();
},
[db]
);
const addTable: StorageContext['addTable'] = useCallback(
async ({ diagramId, table }) => {
await db.db_tables.add({
@@ -810,9 +756,6 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
deleteCustomType,
listCustomTypes,
deleteDiagramCustomTypes,
getDiagramFilter,
updateDiagramFilter,
deleteDiagramFilter,
}}
>
{children}

View File

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

View File

@@ -35,34 +35,58 @@ import type { OnChange } from '@monaco-editor/react';
import { useDebounce } from '@/hooks/use-debounce-v2';
import { InstructionsSection } from './instructions-section/instructions-section';
import { parseSQLError } from '@/lib/data/sql-import';
import type { editor, IDisposable } from 'monaco-editor';
import type * as monaco from 'monaco-editor';
import { waitFor } from '@/lib/utils';
import {
validateSQL,
type ValidationResult,
} from '@/lib/data/sql-import/sql-validator';
import { type ValidationResult } from '@/lib/data/sql-import/sql-validator';
import { validateSQL } from '@/lib/data/sql-import/unified-sql-validator';
import { SQLValidationStatus } from './sql-validation-status';
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
import type { ImportMethod } from '@/lib/import-method/import-method';
import { detectImportMethod } from '@/lib/import-method/detect-import-method';
import { verifyDBML } from '@/lib/dbml/dbml-import/verify-dbml';
import {
clearErrorHighlight,
highlightErrorLine,
} from '@/components/code-snippet/dbml/utils';
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 =
'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.';
// Helper to detect if content is likely SQL DDL or JSON
const detectContentType = (content: string): 'query' | 'ddl' | null => {
if (!content || content.trim().length === 0) return null;
// Common SQL DDL keywords
const ddlKeywords = [
'CREATE TABLE',
'ALTER TABLE',
'DROP TABLE',
'CREATE INDEX',
'CREATE VIEW',
'CREATE PROCEDURE',
'CREATE FUNCTION',
'CREATE SCHEMA',
'CREATE DATABASE',
];
const upperContent = content.toUpperCase();
// Check for SQL DDL patterns
const hasDDLKeywords = ddlKeywords.some((keyword) =>
upperContent.includes(keyword)
);
if (hasDDLKeywords) return 'ddl';
// Check if it looks like JSON
try {
// Just check structure, don't need full parse for detection
if (
(content.trim().startsWith('{') && content.trim().endsWith('}')) ||
(content.trim().startsWith('[') && content.trim().endsWith(']'))
) {
return 'query';
}
} catch (error) {
// Not valid JSON, might be partial
console.error('Error detecting content type:', error);
}
// If we can't confidently detect, return null
return null;
};
export interface ImportDatabaseProps {
goBack?: () => void;
onImport: () => void;
@@ -76,8 +100,8 @@ export interface ImportDatabaseProps {
>;
keepDialogAfterImport?: boolean;
title: string;
importMethod: ImportMethod;
setImportMethod: (method: ImportMethod) => void;
importMethod: 'query' | 'ddl';
setImportMethod: (method: 'query' | 'ddl') => void;
}
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
@@ -96,9 +120,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
}) => {
const { effectiveTheme } = useTheme();
const [errorMessage, setErrorMessage] = useState('');
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const decorationsCollection = useRef<editor.IEditorDecorationsCollection>();
const pasteDisposableRef = useRef<IDisposable | null>(null);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
const pasteDisposableRef = useRef<monaco.IDisposable | null>(null);
const { t } = useTranslation();
const { isSm: isDesktop } = useBreakpoint('sm');
@@ -112,20 +135,15 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const [isAutoFixing, setIsAutoFixing] = useState(false);
const [showAutoFixButton, setShowAutoFixButton] = useState(false);
const clearDecorations = useCallback(() => {
clearErrorHighlight(decorationsCollection.current);
}, []);
useEffect(() => {
setScriptResult('');
setErrorMessage('');
setShowCheckJsonButton(false);
}, [importMethod, setScriptResult]);
// Check if the ddl or dbml is valid
// Check if the ddl is valid
useEffect(() => {
clearDecorations();
if (importMethod === 'query') {
if (importMethod !== 'ddl') {
setSqlValidation(null);
setShowAutoFixButton(false);
return;
@@ -134,54 +152,9 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
if (!scriptResult.trim()) {
setSqlValidation(null);
setShowAutoFixButton(false);
setErrorMessage('');
return;
}
if (importMethod === 'dbml') {
// Validate DBML by parsing it
const validateResponse = verifyDBML(scriptResult);
if (!validateResponse.hasError) {
setErrorMessage('');
setSqlValidation({
isValid: true,
errors: [],
warnings: [],
});
} else {
let errorMsg = 'Invalid DBML syntax';
let line: number = 1;
if (validateResponse.parsedError) {
errorMsg = validateResponse.parsedError.message;
line = validateResponse.parsedError.line;
highlightErrorLine({
error: validateResponse.parsedError,
model: editorRef.current?.getModel(),
editorDecorationsCollection:
decorationsCollection.current,
});
}
setSqlValidation({
isValid: false,
errors: [
{
message: errorMsg,
line: line,
type: 'syntax' as const,
},
],
warnings: [],
});
setErrorMessage(errorMsg);
}
setShowAutoFixButton(false);
return;
}
// SQL validation
// First run our validation based on database type
const validation = validateSQL(scriptResult, databaseType);
setSqlValidation(validation);
@@ -208,7 +181,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
setErrorMessage(result.error);
}
});
}, [importMethod, scriptResult, databaseType, clearDecorations]);
}, [importMethod, scriptResult, databaseType]);
// Check if the script result is a valid JSON
useEffect(() => {
@@ -247,15 +220,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
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);
setIsAutoFixing(false);
}, 1000);
}
}, [sqlValidation, setScriptResult]);
@@ -271,16 +240,6 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const formatEditor = useCallback(() => {
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(() => {
editorRef.current
?.getAction('editor.action.formatDocument')
@@ -334,10 +293,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
}, []);
const handleEditorDidMount = useCallback(
(editor: editor.IStandaloneCodeEditor) => {
(editor: monaco.editor.IStandaloneCodeEditor) => {
editorRef.current = editor;
decorationsCollection.current =
editor.createDecorationsCollection();
// Cleanup previous disposable if it exists
if (pasteDisposableRef.current) {
@@ -352,17 +309,14 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
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 = detectImportMethod(content);
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) {
// Only format if it's JSON (query mode)
if (detectedType === 'query') {
// For JSON mode, format after a short delay
setTimeout(() => {
editor
@@ -370,18 +324,18 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
?.run();
}, 100);
}
// For DDL and DBML modes, do NOT format as it can break the syntax
// 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
if (importMethod === 'query') {
// Only format JSON content
setTimeout(() => {
editor
.getAction('editor.action.formatDocument')
?.run();
}, 100);
}
// For DDL and DBML modes or large files, do NOT format
// For DDL mode, do NOT format
}
});
@@ -428,25 +382,16 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
<div className="w-full text-center text-xs text-muted-foreground">
{importMethod === 'query'
? 'Smart Query Output'
: importMethod === 'dbml'
? 'DBML Script'
: 'SQL Script'}
: 'SQL Script'}
</div>
<div className="flex-1 overflow-hidden">
<Suspense fallback={<Spinner />}>
<Editor
value={scriptResult}
onChange={debouncedHandleInputChange}
language={
importMethod === 'query'
? 'json'
: importMethod === 'dbml'
? 'dbml'
: 'sql'
}
language={importMethod === 'query' ? 'json' : 'sql'}
loading={<Spinner />}
onMount={handleEditorDidMount}
beforeMount={setupDBMLLanguage}
theme={
effectiveTheme === 'dark'
? 'dbml-dark'
@@ -457,6 +402,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
minimap: { enabled: false },
scrollBeyondLastLine: false,
automaticLayout: true,
glyphMargin: false,
lineNumbers: 'on',
guides: {
indentation: false,
@@ -481,15 +427,21 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
</Suspense>
</div>
{errorMessage ||
((importMethod === 'ddl' || importMethod === 'dbml') &&
sqlValidation) ? (
<SQLValidationStatus
validation={sqlValidation}
errorMessage={errorMessage}
isAutoFixing={isAutoFixing}
onErrorClick={handleErrorClick}
/>
{errorMessage || (importMethod === 'ddl' && sqlValidation) ? (
importMethod === 'ddl' ? (
<SQLValidationStatus
validation={sqlValidation}
errorMessage={errorMessage}
isAutoFixing={isAutoFixing}
onErrorClick={handleErrorClick}
/>
) : (
<div className="mt-2 flex shrink-0 items-center gap-2">
<p className="text-xs text-red-700">
{errorMessage}
</p>
</div>
)
) : null}
</div>
),
@@ -594,7 +546,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
variant="secondary"
onClick={handleAutoFix}
disabled={isAutoFixing}
className="bg-sky-600 text-white hover:bg-sky-700"
className="bg-blue-600 text-white hover:bg-blue-700"
>
{isAutoFixing ? (
<Spinner size="small" />

View File

@@ -15,11 +15,9 @@ import {
AvatarImage,
} from '@/components/avatar/avatar';
import { useTranslation } from 'react-i18next';
import { Code, FileCode } from 'lucide-react';
import { Code } from 'lucide-react';
import { SmartQueryInstructions } from './instructions/smart-query-instructions';
import { DDLInstructions } from './instructions/ddl-instructions';
import { DBMLInstructions } from './instructions/dbml-instructions';
import type { ImportMethod } from '@/lib/import-method/import-method';
const DatabasesWithoutDDLInstructions: DatabaseType[] = [
DatabaseType.CLICKHOUSE,
@@ -32,8 +30,8 @@ export interface InstructionsSectionProps {
setDatabaseEdition: React.Dispatch<
React.SetStateAction<DatabaseEdition | undefined>
>;
importMethod: ImportMethod;
setImportMethod: (method: ImportMethod) => void;
importMethod: 'query' | 'ddl';
setImportMethod: (method: 'query' | 'ddl') => void;
showSSMSInfoDialog: boolean;
setShowSSMSInfoDialog: (show: boolean) => void;
}
@@ -127,9 +125,9 @@ export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
className="ml-1 flex-wrap justify-start gap-2"
value={importMethod}
onValueChange={(value) => {
let selectedImportMethod: ImportMethod = 'query';
let selectedImportMethod: 'query' | 'ddl' = 'query';
if (value) {
selectedImportMethod = value as ImportMethod;
selectedImportMethod = value as 'query' | 'ddl';
}
setImportMethod(selectedImportMethod);
@@ -150,21 +148,11 @@ export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
value="ddl"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
>
<Avatar className="size-4 rounded-none">
<FileCode size={16} />
</Avatar>
SQL Script
</ToggleGroupItem>
<ToggleGroupItem
value="dbml"
variant="outline"
className="h-6 gap-1 p-0 px-2 shadow-none data-[state=on]:bg-slate-200 dark:data-[state=on]:bg-slate-700"
>
<Avatar className="size-4 rounded-none">
<Code size={16} />
</Avatar>
DBML
SQL Script
</ToggleGroupItem>
</ToggleGroup>
</div>
@@ -179,13 +167,8 @@ export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
showSSMSInfoDialog={showSSMSInfoDialog}
setShowSSMSInfoDialog={setShowSSMSInfoDialog}
/>
) : importMethod === 'ddl' ? (
<DDLInstructions
databaseType={databaseType}
databaseEdition={databaseEdition}
/>
) : (
<DBMLInstructions
<DDLInstructions
databaseType={databaseType}
databaseEdition={databaseEdition}
/>

View File

@@ -1,47 +0,0 @@
import React from 'react';
import type { DatabaseType } from '@/lib/domain/database-type';
import type { DatabaseEdition } from '@/lib/domain/database-edition';
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
export interface DBMLInstructionsProps {
databaseType: DatabaseType;
databaseEdition?: DatabaseEdition;
}
export const DBMLInstructions: React.FC<DBMLInstructionsProps> = () => {
return (
<>
<div className="flex flex-col gap-1 text-sm text-primary">
<div>
Paste your DBML (Database Markup Language) schema definition
here
</div>
</div>
<div className="flex h-64 flex-col gap-1 text-sm text-primary">
<h4 className="text-xs font-medium">Example:</h4>
<CodeSnippet
className="h-full"
allowCopy={false}
editorProps={{
beforeMount: setupDBMLLanguage,
}}
code={`Table users {
id int [pk]
username varchar
email varchar
}
Table posts {
id int [pk]
user_id int [ref: > users.id]
title varchar
content text
}`}
language={'dbml'}
/>
</div>
</>
);
};

View File

@@ -43,8 +43,8 @@ const DDLInstructionsMap: Record<DatabaseType, DDLInstruction[]> = {
},
{
text: 'Execute the following command in your terminal:',
code: `sqlite3 <database_file_path>\n".schema" > <output_file_path>`,
example: `sqlite3 my_db.db\n".schema" > schema_export.sql`,
code: `sqlite3 <database_file_path>\n.dump > <output_file_path>`,
example: `sqlite3 my_db.db\n.dump > schema_export.sql`,
},
{
text: 'Open the exported SQL file, copy its contents, and paste them here.',

View File

@@ -1,13 +1,15 @@
import React, { useMemo } from 'react';
import { CheckCircle, AlertTriangle, MessageCircleWarning } from 'lucide-react';
import React from 'react';
import {
AlertCircle,
CheckCircle,
AlertTriangle,
Lightbulb,
} 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;
validation: ValidationResult | null;
errorMessage: string;
isAutoFixing?: boolean;
onErrorClick?: (line: number) => void;
@@ -19,113 +21,71 @@ export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
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>
</>
);
}
const hasErrors = validation?.errors && validation.errors.length > 0;
const hasWarnings = validation?.warnings && validation.warnings.length > 0;
const wasAutoFixed =
validation?.warnings?.some((w) => w.message.includes('Auto-fixed')) ||
false;
// 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>
</>
<Alert variant="destructive" className="mt-2">
<AlertCircle className="size-4" />
<AlertDescription className="text-sm">
{errorMessage}
</AlertDescription>
</Alert>
);
}
return (
<>
<Separator className="mb-1 mt-2" />
<div className="mt-2 space-y-2">
{isAutoFixing && (
<Alert className="border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950">
<Lightbulb className="size-4 animate-pulse text-blue-600 dark:text-blue-400" />
<AlertDescription className="text-sm text-blue-700 dark:text-blue-300">
Auto-fixing SQL syntax errors...
</AlertDescription>
</Alert>
)}
{hasErrors ? (
<div className="rounded-md border border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950">
<ScrollArea className="h-fit max-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>
{hasErrors && !isAutoFixing && (
<Alert variant="destructive">
<AlertCircle className="size-4" />
<AlertDescription className="space-y-1 text-sm">
<div className="font-medium">SQL Syntax Errors:</div>
{validation.errors.slice(0, 3).map((error, idx) => (
<div key={idx} className="ml-2">
{' '}
<button
onClick={() => onErrorClick?.(error.line)}
className="rounded underline hover:text-red-600 focus:outline-none focus:ring-1 focus:ring-red-500"
type="button"
>
Line {error.line}
</button>
: {error.message}
{error.suggestion && (
<div className="ml-4 text-xs opacity-80">
{error.suggestion}
</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}
)}
</div>
))}
{validation.errors.length > 3 && (
<div className="ml-2 text-xs opacity-70">
... and {validation.errors.length - 3} more
errors
</div>
)}
</AlertDescription>
</Alert>
)}
{wasAutoFixed && !hasErrors ? (
{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">
@@ -133,47 +93,30 @@ export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
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-fit max-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>
{hasWarnings && !hasErrors && (
<Alert>
<AlertTriangle className="size-4" />
<AlertDescription className="space-y-1 text-sm">
<div className="font-medium">Import Info:</div>
{validation.warnings.map((warning, idx) => (
<div key={idx} className="ml-2">
{warning.message}
</div>
</div>
</ScrollArea>
</div>
) : null}
))}
</AlertDescription>
</Alert>
)}
{!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}
</>
{!hasErrors && !hasWarnings && !errorMessage && validation && (
<Alert className="flex border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950 [&>svg]:static [&>svg]:mr-2 [&>svg~*]:pl-0">
<CheckCircle className="size-4 text-green-600 dark:text-green-400" />
<AlertDescription className="text-sm text-green-700 dark:text-green-300">
SQL syntax validated successfully
</AlertDescription>
</Alert>
)}
</div>
);
};

View File

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

View File

@@ -1,683 +0,0 @@
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,5 +1,4 @@
export enum CreateDiagramDialogStep {
SELECT_DATABASE = 'SELECT_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 { useStorage } from '@/hooks/use-storage';
import type { Diagram } from '@/lib/domain/diagram';
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
import { useNavigate } from 'react-router-dom';
import { useConfig } from '@/hooks/use-config';
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
@@ -15,18 +15,9 @@ import type { DatabaseEdition } from '@/lib/domain/database-edition';
import { SelectDatabase } from './select-database/select-database';
import { CreateDiagramDialogStep } from './create-diagram-dialog-step';
import { ImportDatabase } from '../common/import-database/import-database';
import { SelectTables } from '../common/select-tables/select-tables';
import { useTranslation } from 'react-i18next';
import type { BaseDialogProps } from '../common/base-dialog-props';
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';
import {
defaultDBMLDiagramName,
importDBMLToDiagram,
} from '@/lib/dbml/dbml-import/dbml-import';
import type { ImportMethod } from '@/lib/import-method/import-method';
export interface CreateDiagramDialogProps extends BaseDialogProps {}
@@ -35,11 +26,11 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
}) => {
const { diagramId } = useChartDB();
const { t } = useTranslation();
const [importMethod, setImportMethod] = useState<ImportMethod>('query');
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
const [databaseType, setDatabaseType] = useState<DatabaseType>(
DatabaseType.GENERIC
);
const { closeCreateDiagramDialog } = useDialog();
const { closeCreateDiagramDialog, openImportDBMLDialog } = useDialog();
const { updateConfig } = useConfig();
const [scriptResult, setScriptResult] = useState('');
const [databaseEdition, setDatabaseEdition] = useState<
@@ -51,8 +42,6 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
const { listDiagrams, addDiagram } = useStorage();
const [diagramNumber, setDiagramNumber] = useState<number>(1);
const navigate = useNavigate();
const [parsedMetadata, setParsedMetadata] = useState<DatabaseMetadata>();
const [isParsingMetadata, setIsParsingMetadata] = useState(false);
useEffect(() => {
setDatabaseEdition(undefined);
@@ -73,80 +62,49 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
setDatabaseEdition(undefined);
setScriptResult('');
setImportMethod('query');
setParsedMetadata(undefined);
}, [dialog.open]);
const hasExistingDiagram = (diagramId ?? '').trim().length !== 0;
const importNewDiagram = useCallback(
async ({
selectedTables,
databaseMetadata,
}: {
selectedTables?: SelectedTable[];
databaseMetadata?: DatabaseMetadata;
} = {}) => {
let diagram: Diagram | undefined;
const importNewDiagram = useCallback(async () => {
let diagram: Diagram | undefined;
if (importMethod === 'ddl') {
diagram = await sqlImportToDiagram({
sqlContent: scriptResult,
sourceDatabaseType: databaseType,
targetDatabaseType: databaseType,
});
} else if (importMethod === 'dbml') {
diagram = await importDBMLToDiagram(scriptResult, {
databaseType,
});
// Update the diagram name if it's the default
if (diagram.name === defaultDBMLDiagramName) {
diagram.name = `Diagram ${diagramNumber}`;
}
} 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 },
if (importMethod === 'ddl') {
diagram = await sqlImportToDiagram({
sqlContent: scriptResult,
sourceDatabaseType: databaseType,
targetDatabaseType: databaseType,
});
} else {
const databaseMetadata: DatabaseMetadata =
loadDatabaseMetadata(scriptResult);
closeCreateDiagramDialog();
navigate(`/diagrams/${diagram.id}`);
},
[
importMethod,
databaseType,
addDiagram,
databaseEdition,
closeCreateDiagramDialog,
navigate,
updateConfig,
scriptResult,
diagramNumber,
]
);
diagram = await loadFromDatabaseMetadata({
databaseType,
databaseMetadata,
diagramNumber,
databaseEdition:
databaseEdition?.trim().length === 0
? undefined
: databaseEdition,
});
}
await addDiagram({ diagram });
await updateConfig({ config: { defaultDiagramId: diagram.id } });
closeCreateDiagramDialog();
navigate(`/diagrams/${diagram.id}`);
}, [
importMethod,
databaseType,
addDiagram,
databaseEdition,
closeCreateDiagramDialog,
navigate,
updateConfig,
scriptResult,
diagramNumber,
]);
const createEmptyDiagram = useCallback(async () => {
const diagram: Diagram = {
@@ -165,6 +123,10 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
await updateConfig({ config: { defaultDiagramId: diagram.id } });
closeCreateDiagramDialog();
navigate(`/diagrams/${diagram.id}`);
setTimeout(
() => openImportDBMLDialog({ withCreateEmptyDiagram: true }),
700
);
}, [
databaseType,
addDiagram,
@@ -173,58 +135,13 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
navigate,
updateConfig,
diagramNumber,
openImportDBMLDialog,
]);
const importNewDiagramOrFilterTables = useCallback(async () => {
try {
setIsParsingMetadata(true);
if (importMethod === 'ddl' || importMethod === 'dbml') {
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 (
<Dialog
{...dialog}
onOpenChange={(open) => {
// Don't allow closing while parsing metadata
if (isParsingMetadata) {
return;
}
if (!hasExistingDiagram) {
return;
}
@@ -237,8 +154,6 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
<DialogContent
className="flex max-h-dvh w-full flex-col md:max-w-[900px]"
showClose={hasExistingDiagram}
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
<SelectDatabase
@@ -250,9 +165,9 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
}
/>
) : step === CreateDiagramDialogStep.IMPORT_DATABASE ? (
) : (
<ImportDatabase
onImport={importNewDiagramOrFilterTables}
onImport={importNewDiagram}
onCreateEmptyDiagram={createEmptyDiagram}
databaseEdition={databaseEdition}
databaseType={databaseType}
@@ -265,18 +180,8 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
title={t('new_diagram_dialog.import_database.title')}
importMethod={importMethod}
setImportMethod={setImportMethod}
keepDialogAfterImport={true}
/>
) : step === CreateDiagramDialogStep.SELECT_TABLES ? (
<SelectTables
isLoading={isParsingMetadata || !parsedMetadata}
databaseMetadata={parsedMetadata}
onImport={importNewDiagram}
onBack={() =>
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
}
/>
) : null}
)}
</DialogContent>
</Dialog>
);

View File

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

View File

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

View File

@@ -17,21 +17,15 @@ import { useDialog } from '@/hooks/use-dialog';
import {
exportBaseSQL,
exportSQL,
} from '@/lib/data/sql-export/export-sql-script';
} from '@/lib/data/export-metadata/export-sql-script';
import { databaseTypeToLabelMap } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type';
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
import { Annoyed, Sparkles } from 'lucide-react';
import React, { useCallback, useEffect, useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import type { BaseDialogProps } from '../common/base-dialog-props';
import type { Diagram } from '@/lib/domain/diagram';
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 {
targetDatabaseType: DatabaseType;
@@ -42,8 +36,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
targetDatabaseType,
}) => {
const { closeExportSQLDialog } = useDialog();
const { currentDiagram } = useChartDB();
const { filter } = useDiagramFilter();
const { currentDiagram, filteredSchemas } = useChartDB();
const { t } = useTranslation();
const [script, setScript] = React.useState<string>();
const [error, setError] = React.useState<boolean>(false);
@@ -55,16 +48,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
const filteredDiagram: Diagram = {
...currentDiagram,
tables: currentDiagram.tables?.filter((table) =>
filterTable({
table: {
id: table.id,
schema: table.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
shouldShowTablesBySchemaFilter(table, filteredSchemas)
),
relationships: currentDiagram.relationships?.filter((rel) => {
const sourceTable = currentDiagram.tables?.find(
@@ -76,20 +60,11 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return (
sourceTable &&
targetTable &&
filterRelationship({
tableA: {
id: sourceTable.id,
schema: sourceTable.schema,
},
tableB: {
id: targetTable.id,
schema: targetTable.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
shouldShowTablesBySchemaFilter(
sourceTable,
filteredSchemas
) &&
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
);
}),
dependencies: currentDiagram.dependencies?.filter((dep) => {
@@ -102,20 +77,11 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
return (
table &&
dependentTable &&
filterDependency({
tableA: {
id: table.id,
schema: table.schema,
},
tableB: {
id: dependentTable.id,
schema: dependentTable.schema,
},
filter,
options: {
defaultSchema: defaultSchemas[targetDatabaseType],
},
})
shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
shouldShowTablesBySchemaFilter(
dependentTable,
filteredSchemas
)
);
}),
};
@@ -135,7 +101,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
signal: abortControllerRef.current?.signal,
});
}
}, [targetDatabaseType, currentDiagram, filter]);
}, [targetDatabaseType, currentDiagram, filteredSchemas]);
useEffect(() => {
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 { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
import type { Diagram } from '@/lib/domain/diagram';
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
import { useChartDB } from '@/hooks/use-chartdb';
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
import { Trans, useTranslation } from 'react-i18next';
@@ -15,8 +15,6 @@ import { useReactFlow } from '@xyflow/react';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useAlert } from '@/context/alert-context/alert-context';
import { sqlImportToDiagram } from '@/lib/data/sql-import';
import { importDBMLToDiagram } from '@/lib/dbml/dbml-import/dbml-import';
import type { ImportMethod } from '@/lib/import-method/import-method';
export interface ImportDatabaseDialogProps extends BaseDialogProps {
databaseType: DatabaseType;
@@ -26,7 +24,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
dialog,
databaseType,
}) => {
const [importMethod, setImportMethod] = useState<ImportMethod>('query');
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
const { closeImportDatabaseDialog } = useDialog();
const { showAlert } = useAlert();
const {
@@ -60,17 +58,16 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
const importDatabase = useCallback(async () => {
let diagram: Diagram | undefined;
let warnings: string[] | undefined;
if (importMethod === 'ddl') {
diagram = await sqlImportToDiagram({
const result = await sqlImportToDiagram({
sqlContent: scriptResult,
sourceDatabaseType: databaseType,
targetDatabaseType: databaseType,
});
} else if (importMethod === 'dbml') {
diagram = await importDBMLToDiagram(scriptResult, {
databaseType,
});
diagram = result;
warnings = result.warnings;
} else {
const databaseMetadata: DatabaseMetadata =
loadDatabaseMetadata(scriptResult);
@@ -325,7 +322,38 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
resetRedoStack();
resetUndoStack();
closeImportDatabaseDialog();
// Show warnings if any
if (warnings && warnings.length > 0) {
const warningContent = (
<div className="space-y-2">
<div className="font-semibold">
The following SQL statements were skipped:
</div>
<ul className="list-inside list-disc space-y-1">
{warnings.map((warning, index) => (
<li key={index} className="text-sm">
{warning}
</li>
))}
</ul>
<div className="mt-3 text-sm text-muted-foreground">
Only table definitions, indexes, and foreign key
constraints are currently supported.
</div>
</div>
);
showAlert({
title: 'Import completed with warnings',
content: warningContent,
actionLabel: 'OK',
onAction: () => {
closeImportDatabaseDialog();
},
});
} else {
closeImportDatabaseDialog();
}
}, [
importMethod,
databaseEdition,

View File

@@ -0,0 +1,413 @@
import React, {
useCallback,
useEffect,
useState,
Suspense,
useRef,
} from 'react';
import * as monaco from 'monaco-editor';
import { useDialog } from '@/hooks/use-dialog';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogInternalContent,
DialogTitle,
} from '@/components/dialog/dialog';
import { Button } from '@/components/button/button';
import type { BaseDialogProps } from '../common/base-dialog-props';
import { useTranslation } from 'react-i18next';
import { Editor } from '@/components/code-snippet/code-snippet';
import { useTheme } from '@/hooks/use-theme';
import { AlertCircle } from 'lucide-react';
import { importDBMLToDiagram, sanitizeDBML } from '@/lib/dbml-import';
import { useChartDB } from '@/hooks/use-chartdb';
import { Parser } from '@dbml/core';
import { useCanvas } from '@/hooks/use-canvas';
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
import { useToast } from '@/components/toast/use-toast';
import { Spinner } from '@/components/spinner/spinner';
import { debounce } from '@/lib/utils';
interface DBMLError {
message: string;
line: number;
column: number;
}
function parseDBMLError(error: unknown): DBMLError | null {
try {
if (typeof error === 'string') {
const parsed = JSON.parse(error);
if (parsed.diags?.[0]) {
const diag = parsed.diags[0];
return {
message: diag.message,
line: diag.location.start.line,
column: diag.location.start.column,
};
}
} else if (error && typeof error === 'object' && 'diags' in error) {
const parsed = error as {
diags: Array<{
message: string;
location: { start: { line: number; column: number } };
}>;
};
if (parsed.diags?.[0]) {
return {
message: parsed.diags[0].message,
line: parsed.diags[0].location.start.line,
column: parsed.diags[0].location.start.column,
};
}
}
} catch (e) {
console.error('Error parsing DBML error:', e);
}
return null;
}
export interface ImportDBMLDialogProps extends BaseDialogProps {
withCreateEmptyDiagram?: boolean;
}
export const ImportDBMLDialog: React.FC<ImportDBMLDialogProps> = ({
dialog,
withCreateEmptyDiagram,
}) => {
const { t } = useTranslation();
const initialDBML = `// Use DBML to define your database structure
// Simple Blog System with Comments Example
Table users {
id integer [primary key]
name varchar
email varchar
}
Table posts {
id integer [primary key]
title varchar
content text
user_id integer
created_at timestamp
}
Table comments {
id integer [primary key]
content text
post_id integer
user_id integer
created_at timestamp
}
// Relationships
Ref: posts.user_id > users.id // Each post belongs to one user
Ref: comments.post_id > posts.id // Each comment belongs to one post
Ref: comments.user_id > users.id // Each comment is written by one user`;
const [dbmlContent, setDBMLContent] = useState<string>(initialDBML);
const { closeImportDBMLDialog } = useDialog();
const [errorMessage, setErrorMessage] = useState<string | undefined>();
const { effectiveTheme } = useTheme();
const { toast } = useToast();
const {
addTables,
addRelationships,
tables,
relationships,
removeTables,
removeRelationships,
} = useChartDB();
const { reorderTables } = useCanvas();
const [reorder, setReorder] = useState(false);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
const decorationsCollection =
useRef<monaco.editor.IEditorDecorationsCollection>();
const handleEditorDidMount = (
editor: monaco.editor.IStandaloneCodeEditor
) => {
editorRef.current = editor;
decorationsCollection.current = editor.createDecorationsCollection();
};
useEffect(() => {
if (reorder) {
reorderTables({
updateHistory: false,
});
setReorder(false);
}
}, [reorder, reorderTables]);
const highlightErrorLine = useCallback((error: DBMLError) => {
if (!editorRef.current) return;
const model = editorRef.current.getModel();
if (!model) return;
const decorations = [
{
range: new monaco.Range(
error.line,
1,
error.line,
model.getLineMaxColumn(error.line)
),
options: {
isWholeLine: true,
className: 'dbml-error-line',
glyphMarginClassName: 'dbml-error-glyph',
hoverMessage: { value: error.message },
overviewRuler: {
color: '#ff0000',
position: monaco.editor.OverviewRulerLane.Right,
darkColor: '#ff0000',
},
},
},
];
decorationsCollection.current?.set(decorations);
}, []);
const clearDecorations = useCallback(() => {
decorationsCollection.current?.clear();
}, []);
const validateDBML = useCallback(
async (content: string) => {
// Clear previous errors
setErrorMessage(undefined);
clearDecorations();
if (!content.trim()) return;
try {
const sanitizedContent = sanitizeDBML(content);
const parser = new Parser();
parser.parse(sanitizedContent, 'dbml');
} catch (e) {
const parsedError = parseDBMLError(e);
if (parsedError) {
setErrorMessage(
t('import_dbml_dialog.error.description') +
` (1 error found - in line ${parsedError.line})`
);
highlightErrorLine(parsedError);
} else {
setErrorMessage(
e instanceof Error ? e.message : JSON.stringify(e)
);
}
}
},
[clearDecorations, highlightErrorLine, t]
);
const debouncedValidateRef = useRef<((value: string) => void) | null>(null);
// Set up debounced validation
useEffect(() => {
debouncedValidateRef.current = debounce((value: string) => {
validateDBML(value);
}, 500);
return () => {
debouncedValidateRef.current = null;
};
}, [validateDBML]);
// Trigger validation when content changes
useEffect(() => {
if (debouncedValidateRef.current) {
debouncedValidateRef.current(dbmlContent);
}
}, [dbmlContent]);
useEffect(() => {
if (!dialog.open) {
setErrorMessage(undefined);
clearDecorations();
setDBMLContent(initialDBML);
}
}, [dialog.open, initialDBML, clearDecorations]);
const handleImport = useCallback(async () => {
if (!dbmlContent.trim() || errorMessage) return;
try {
// Sanitize DBML content before importing
const sanitizedContent = sanitizeDBML(dbmlContent);
const importedDiagram = await importDBMLToDiagram(sanitizedContent);
const tableIdsToRemove = tables
.filter((table) =>
importedDiagram.tables?.some(
(t) =>
t.name === table.name && t.schema === table.schema
)
)
.map((table) => table.id);
// Find relationships that need to be removed
const relationshipIdsToRemove = relationships
.filter((relationship) => {
const sourceTable = tables.find(
(table) => table.id === relationship.sourceTableId
);
const targetTable = tables.find(
(table) => table.id === relationship.targetTableId
);
if (!sourceTable || !targetTable) return true;
const replacementSourceTable = importedDiagram.tables?.find(
(table) =>
table.name === sourceTable.name &&
table.schema === sourceTable.schema
);
const replacementTargetTable = importedDiagram.tables?.find(
(table) =>
table.name === targetTable.name &&
table.schema === targetTable.schema
);
return replacementSourceTable || replacementTargetTable;
})
.map((relationship) => relationship.id);
// Remove existing items
await Promise.all([
removeTables(tableIdsToRemove, { updateHistory: false }),
removeRelationships(relationshipIdsToRemove, {
updateHistory: false,
}),
]);
// Add new items
await Promise.all([
addTables(importedDiagram.tables ?? [], {
updateHistory: false,
}),
addRelationships(importedDiagram.relationships ?? [], {
updateHistory: false,
}),
]);
setReorder(true);
closeImportDBMLDialog();
} catch (e) {
toast({
title: t('import_dbml_dialog.error.title'),
variant: 'destructive',
description: (
<>
<div>{t('import_dbml_dialog.error.description')}</div>
{e instanceof Error ? e.message : JSON.stringify(e)}
</>
),
});
}
}, [
dbmlContent,
closeImportDBMLDialog,
tables,
relationships,
removeTables,
removeRelationships,
addTables,
addRelationships,
errorMessage,
toast,
setReorder,
t,
]);
return (
<Dialog
{...dialog}
onOpenChange={(open) => {
if (!open) {
closeImportDBMLDialog();
}
}}
>
<DialogContent
className="flex h-[80vh] max-h-screen w-full flex-col md:max-w-[900px]"
showClose
>
<DialogHeader>
<DialogTitle>
{withCreateEmptyDiagram
? t('import_dbml_dialog.example_title')
: t('import_dbml_dialog.title')}
</DialogTitle>
<DialogDescription>
{t('import_dbml_dialog.description')}
</DialogDescription>
</DialogHeader>
<DialogInternalContent>
<Suspense fallback={<Spinner />}>
<Editor
value={dbmlContent}
onChange={(value) => setDBMLContent(value || '')}
language="dbml"
onMount={handleEditorDidMount}
theme={
effectiveTheme === 'dark'
? 'dbml-dark'
: 'dbml-light'
}
beforeMount={setupDBMLLanguage}
options={{
minimap: { enabled: false },
scrollBeyondLastLine: false,
automaticLayout: true,
glyphMargin: true,
lineNumbers: 'on',
scrollbar: {
vertical: 'visible',
horizontal: 'visible',
},
}}
className="size-full"
/>
</Suspense>
</DialogInternalContent>
<DialogFooter>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-4">
<DialogClose asChild>
<Button variant="secondary">
{withCreateEmptyDiagram
? t('import_dbml_dialog.skip_and_empty')
: t('import_dbml_dialog.cancel')}
</Button>
</DialogClose>
{errorMessage ? (
<div className="flex items-center gap-1">
<AlertCircle className="size-4 text-destructive" />
<span className="text-xs text-destructive">
{errorMessage ||
t(
'import_dbml_dialog.error.description'
)}
</span>
</div>
) : null}
</div>
<Button
onClick={handleImport}
disabled={!dbmlContent.trim() || !!errorMessage}
>
{withCreateEmptyDiagram
? t('import_dbml_dialog.show_example')
: t('import_dbml_dialog.import')}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

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

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useDialog } from '@/hooks/use-dialog';
import {
Dialog,
@@ -17,23 +17,11 @@ import type { DBSchema } from '@/lib/domain/db-schema';
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
import type { BaseDialogProps } from '../common/base-dialog-props';
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 {
table?: DBTable;
schemas: DBSchema[];
onConfirm: ({ schema }: { schema: DBSchema }) => void;
allowSchemaCreation?: boolean;
onConfirm: (schema: string) => void;
}
export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
@@ -41,73 +29,27 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
table,
schemas,
onConfirm,
allowSchemaCreation = false,
}) => {
const { t } = useTranslation();
const { databaseType } = useChartDB();
const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
const [selectedSchema, setSelectedSchema] = React.useState<string>(
table?.schema
? schemaNameToSchemaId(table.schema)
: (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(() => {
if (!dialog.open) return;
setSelectedSchemaId(
setSelectedSchema(
table?.schema
? schemaNameToSchemaId(table.schema)
: (schemas?.[0]?.id ?? '')
);
setIsCreatingNew(!allowSchemaSelection);
setNewSchemaName(
allowSchemaCreation && !allowSchemaSelection
? (defaultSchemaName ?? '')
: ''
);
}, [
defaultSchemaName,
dialog.open,
schemas,
table?.schema,
allowSchemaSelection,
allowSchemaCreation,
]);
}, [dialog.open, schemas, table?.schema]);
const { closeTableSchemaDialog } = useDialog();
const handleConfirm = useCallback(() => {
if (isCreatingNew && newSchemaName.trim()) {
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]);
onConfirm(selectedSchema);
}, [onConfirm, selectedSchema]);
const schemaOptions: SelectBoxOption[] = useMemo(
() =>
@@ -118,25 +60,6 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
[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 (
<Dialog
{...dialog}
@@ -144,106 +67,48 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
if (!open) {
closeTableSchemaDialog();
}
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
}}
>
<DialogContent className="flex flex-col" showClose>
<DialogHeader>
<DialogTitle>
{!allowSchemaSelection && allowSchemaCreation
? t('create_table_schema_dialog.title')
: table
? t('update_table_schema_dialog.title')
: t('new_table_schema_dialog.title')}
{table
? t('update_table_schema_dialog.title')
: t('new_table_schema_dialog.title')}
</DialogTitle>
<DialogDescription>
{!allowSchemaSelection && allowSchemaCreation
? t('create_table_schema_dialog.description')
: table
? t('update_table_schema_dialog.description', {
tableName: table.name,
})
: t('new_table_schema_dialog.description')}
{table
? t('update_table_schema_dialog.description', {
tableName: table.name,
})
: t('new_table_schema_dialog.description')}
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-1">
<div className="grid w-full items-center gap-4">
{!isCreatingNew ? (
<SelectBox
options={schemaOptions}
multiple={false}
value={selectedSchemaId}
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}
<SelectBox
options={schemaOptions}
multiple={false}
value={selectedSchema}
onChange={(value) =>
setSelectedSchema(value as string)
}
/>
</div>
</div>
<DialogFooter className="flex gap-1 md:justify-between">
<DialogClose asChild>
<Button variant="secondary">
{isCreatingNew
? t('create_table_schema_dialog.cancel')
: table
? t('update_table_schema_dialog.cancel')
: t('new_table_schema_dialog.cancel')}
{table
? t('update_table_schema_dialog.cancel')
: t('new_table_schema_dialog.cancel')}
</Button>
</DialogClose>
<DialogClose asChild>
<Button
onClick={handleConfirm}
disabled={isCreatingNew && !newSchemaName.trim()}
>
{isCreatingNew
? t('create_table_schema_dialog.create')
: table
? t('update_table_schema_dialog.confirm')
: t('new_table_schema_dialog.confirm')}
<Button onClick={handleConfirm}>
{table
? t('update_table_schema_dialog.confirm')
: t('new_table_schema_dialog.confirm')}
</Button>
</DialogClose>
</DialogFooter>

View File

@@ -83,7 +83,6 @@
}
body {
@apply bg-background text-foreground;
overscroll-behavior-x: none;
}
.text-editable {
@@ -155,29 +154,3 @@
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

@@ -1,142 +0,0 @@
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

@@ -1,320 +0,0 @@
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,25 +2,17 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
export const tr: LanguageTranslation = {
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: {
actions: {
actions: 'Eylemler',
new: 'Yeni...',
browse: 'Gözat...',
file: {
file: 'Dosya',
new: 'Yeni',
open: '',
save: 'Kaydet',
import: 'Veritabanı İçe Aktar',
export_sql: 'SQL Olarak Dışa Aktar',
export_as: 'Olarak Dışa Aktar',
delete_diagram: 'Sil',
delete_diagram: 'Diyagramı Sil',
exit: ıkış',
},
edit: {
edit: 'Düzenle',
@@ -34,10 +26,7 @@ export const tr: LanguageTranslation = {
hide_sidebar: 'Kenar Çubuğunu Gizle',
hide_cardinality: 'Kardinaliteyi Gizle',
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',
show_views: 'Veritabanı Görünümleri',
theme: 'Tema',
show_dependencies: 'Bağımlılıkları Göster',
hide_dependencies: 'Bağımlılıkları Gizle',
@@ -75,13 +64,22 @@ export const tr: LanguageTranslation = {
},
reorder_diagram_alert: {
title: 'Diyagramı Otomatik Düzenle',
title: 'Diyagramı Yeniden Sırala',
description:
'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?',
reorder: 'Otomatik Düzenle',
reorder: 'Yeniden Sırala',
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: {
unsupported: {
title: 'Kopyalama başarısız',
@@ -115,11 +113,14 @@ export const tr: LanguageTranslation = {
copy_to_clipboard: 'Panoya Kopyala',
copied: 'Kopyalandı!',
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...',
tables_section: {
tables: 'Tablolar',
add_table: 'Tablo Ekle',
add_view: 'Görünüm Ekle',
filter: 'Filtrele',
collapse: 'Hepsini Daralt',
// TODO: Translate
@@ -145,23 +146,16 @@ export const tr: LanguageTranslation = {
field_actions: {
title: 'Alan Özellikleri',
unique: 'Tekil',
auto_increment: 'Otomatik Artış',
comments: 'Yorumlar',
no_comments: 'Yorum yok',
delete_field: 'Alanı Sil',
// TODO: Translate
default_value: 'Default Value',
no_default: 'No default',
// TODO: Translate
character_length: 'Max Length',
precision: 'Hassasiyet',
scale: 'Ölçek',
},
index_actions: {
title: 'İndeks Özellikleri',
name: 'Ad',
unique: 'Tekil',
index_type: 'İndeks Türü',
delete_index: 'İndeksi Sil',
},
table_actions: {
@@ -179,15 +173,12 @@ export const tr: LanguageTranslation = {
description: 'Başlamak için bir tablo oluşturun',
},
},
refs_section: {
refs: 'Refs',
filter: 'Filtrele',
collapse: 'Hepsini Daralt',
add_relationship: 'İlişki Ekle',
relationships_section: {
relationships: 'İlişkiler',
dependencies: 'Bağımlılıklar',
filter: 'Filtrele',
add_relationship: 'İlişki Ekle',
collapse: 'Hepsini Daralt',
relationship: {
relationship: 'İlişki',
primary: 'Birincil Tablo',
foreign: 'Referans Tablo',
cardinality: 'Kardinalite',
@@ -197,8 +188,16 @@ export const tr: LanguageTranslation = {
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: 'Bağımlılık',
table: 'Tablo',
dependent_table: 'Bağımlı Görünüm',
delete_dependency: 'Sil',
@@ -208,8 +207,8 @@ export const tr: LanguageTranslation = {
},
},
empty_state: {
title: 'İlişki yok',
description: 'Başlamak için bir ilişki oluşturun',
title: 'Bağımlılık yok',
description: 'Başlamak için bir görünüm oluşturun',
},
},
@@ -249,16 +248,12 @@ export const tr: LanguageTranslation = {
enum_values: 'Enum Values',
composite_fields: 'Fields',
no_fields: 'No fields defined',
no_values: 'Tanımlanmış enum değeri yok',
field_name_placeholder: 'Field name',
field_type_placeholder: 'Select type',
add_field: 'Add Field',
no_fields_tooltip: 'No fields defined for this custom type',
custom_type_actions: {
title: 'Actions',
highlight_fields: 'Highlight Fields',
delete_custom_type: 'Delete',
clear_field_highlight: 'Clear Highlight',
},
delete_custom_type: 'Delete Type',
},
@@ -271,14 +266,8 @@ export const tr: LanguageTranslation = {
show_all: 'Hepsini Gör',
undo: 'Geri Al',
redo: 'Yinele',
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',
reorder_diagram: 'Diyagramı Yeniden Sırala',
highlight_overlapping_tables: 'Çakışan Tabloları Vurgula',
// TODO: Translate
filter: 'Filter Tables',
},
new_diagram_dialog: {
database_selection: {
@@ -313,7 +302,7 @@ export const tr: LanguageTranslation = {
import: 'İçe Aktar',
},
open_diagram_dialog: {
title: 'Veritabanı Aç',
title: 'Diyagramı Aç',
description: 'Aşağıdaki listeden açmak için bir diyagram seçin.',
table_columns: {
name: 'Ad',
@@ -323,12 +312,6 @@ export const tr: LanguageTranslation = {
},
cancel: 'İptal',
open: 'Aç',
diagram_actions: {
open: 'Aç',
duplicate: 'Kopyala',
delete: 'Sil',
},
},
export_sql_dialog: {
@@ -409,14 +392,6 @@ export const tr: LanguageTranslation = {
cancel: 'İptal',
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: {
title: 'Bize yardım et!',
description:
@@ -471,7 +446,6 @@ export const tr: LanguageTranslation = {
},
canvas_context_menu: {
new_table: 'Yeni Tablo',
new_view: 'Yeni Görünüm',
new_relationship: 'Yeni İlişki',
// TODO: Translate
new_area: 'New Area',
@@ -494,9 +468,6 @@ export const tr: LanguageTranslation = {
language_select: {
change_language: 'Dil',
},
on: 'Açık',
off: 'Kapalı',
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import type { DBCustomType } from './domain';
import type { Area } from './domain/area';
import type { DBDependency } from './domain/db-dependency';
import type { DBField } from './domain/db-field';
@@ -49,10 +48,6 @@ const generateIdsMapFromDiagram = (
idsMap.set(area.id, generateId());
});
diagram.customTypes?.forEach((customType) => {
idsMap.set(customType.id, generateId());
});
return idsMap;
};
@@ -218,22 +213,6 @@ export const cloneDiagram = (
})
.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 {
diagram: {
...diagram,
@@ -242,7 +221,6 @@ export const cloneDiagram = (
relationships,
tables,
areas,
customTypes,
createdAt: diagram.createdAt
? new Date(diagram.createdAt)
: new Date(),

View File

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

View File

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

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