mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-05 06:23:17 +00:00
Compare commits
160 Commits
jf/fix_sql
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9baecea4ab | ||
|
|
c8b827764c | ||
|
|
0afa71efcc | ||
|
|
69d4e8dca6 | ||
|
|
a4674a2bf8 | ||
|
|
07dc4eace0 | ||
|
|
4fd940afbb | ||
|
|
3d85bcc6ab | ||
|
|
973b7663b1 | ||
|
|
6d38ebe3ec | ||
|
|
68412f90a7 | ||
|
|
084a1d505c | ||
|
|
91e713c30a | ||
|
|
acf6d4b365 | ||
|
|
9e8979d062 | ||
|
|
9ed27cf30c | ||
|
|
2c4b344efb | ||
|
|
ccb29e0a57 | ||
|
|
7d811de097 | ||
|
|
62dec48572 | ||
|
|
49328d8fbd | ||
|
|
459698b5d0 | ||
|
|
7ad0e7712d | ||
|
|
34475add32 | ||
|
|
38fedcec0c | ||
|
|
498655e7b7 | ||
|
|
bcd8aa9378 | ||
|
|
b15bc945ac | ||
|
|
c3c646bf7c | ||
|
|
57b3b8777f | ||
|
|
bb033091b1 | ||
|
|
c9ac8929c5 | ||
|
|
c567c0a5f3 | ||
|
|
2dc1a6fc75 | ||
|
|
98f6edd5c8 | ||
|
|
47a7a73a13 | ||
|
|
d71b46e8b5 | ||
|
|
e4c4a3b354 | ||
|
|
1b8d51b73c | ||
|
|
93d72a896b | ||
|
|
9991077978 | ||
|
|
bc82f9d6a8 | ||
|
|
26dc299cd2 | ||
|
|
d6ba4a4074 | ||
|
|
d09379e8be | ||
|
|
bdc41c0b74 | ||
|
|
d3dbf41894 | ||
|
|
e6783a89cc | ||
|
|
af3638da7a | ||
|
|
8954d893bb | ||
|
|
1a6688e85e | ||
|
|
5e81c1848a | ||
|
|
2bd9ca25b2 | ||
|
|
b016a70691 | ||
|
|
a0fb1ed08b | ||
|
|
ffddcdcc98 | ||
|
|
fe9ef275b8 | ||
|
|
df89f0b6b9 | ||
|
|
534d2858af | ||
|
|
2a64deebb8 | ||
|
|
e5e1d59327 | ||
|
|
aa290615ca | ||
|
|
ec6e46fe81 | ||
|
|
ac128d67de | ||
|
|
07937a2f51 | ||
|
|
d8e0bc7db8 | ||
|
|
1ce265781b | ||
|
|
60c5675cbf | ||
|
|
66b086378c | ||
|
|
abd2a6ccbe | ||
|
|
459c5f1ce3 | ||
|
|
44be48ff3a | ||
|
|
ad8e34483f | ||
|
|
215d57979d | ||
|
|
ec3719ebce | ||
|
|
0a5874a69b | ||
|
|
7e0fdd1595 | ||
|
|
2531a7023f | ||
|
|
73daf0df21 | ||
|
|
c77c983989 | ||
|
|
0aaa451479 | ||
|
|
b697e26170 | ||
|
|
04d91c67b1 | ||
|
|
d0dee84970 | ||
|
|
b4ccfcdcde | ||
|
|
1759b0b9f2 | ||
|
|
ab4845c772 | ||
|
|
0545b41140 | ||
|
|
4520f8b1f7 | ||
|
|
712bdf5b95 | ||
|
|
d7c9536272 | ||
|
|
815a52f192 | ||
|
|
f1a4298362 | ||
|
|
b8f2141bd2 | ||
|
|
eaebe34768 | ||
|
|
0d623a86b1 | ||
|
|
19fd94c6bd | ||
|
|
0da3caeeac | ||
|
|
cb2ba66233 | ||
|
|
8a2267281b | ||
|
|
41ba251377 | ||
|
|
e9c5442d9d | ||
|
|
4f1d3295c0 | ||
|
|
5936500ca0 | ||
|
|
43fc1d7fc2 | ||
|
|
8dfa7cc62e | ||
|
|
23e93bfd01 | ||
|
|
16f9f4671e | ||
|
|
0c300e5e72 | ||
|
|
b9a1e78b53 | ||
|
|
337f7cdab4 | ||
|
|
1b0390f0b7 | ||
|
|
bc52933b58 | ||
|
|
2fdad2344c | ||
|
|
0c7eaa2df2 | ||
|
|
a5f8e56b3c | ||
|
|
8ffde62c1a | ||
|
|
39247b77a2 | ||
|
|
984b2aeee2 | ||
|
|
eed104be5b | ||
|
|
00bd535b3c | ||
|
|
18e914242f | ||
|
|
e68837a34a | ||
|
|
b30162d98b | ||
|
|
dba372d25a | ||
|
|
2eb48e75d3 | ||
|
|
867903cd5f | ||
|
|
8aeb1df0ad | ||
|
|
6bea827293 | ||
|
|
a119854da7 | ||
|
|
bfbfd7b843 | ||
|
|
0ca7008735 | ||
|
|
4bc71c52ff | ||
|
|
8f27f10dec | ||
|
|
a93ec2cab9 | ||
|
|
386e40a0bf | ||
|
|
bda150d4b6 | ||
|
|
87836e53d1 | ||
|
|
7e0483f1a5 | ||
|
|
309ee9cb0f | ||
|
|
79b885502e | ||
|
|
745bdee86d | ||
|
|
08eb9cc55f | ||
|
|
778f85d492 | ||
|
|
fb92be7d3e | ||
|
|
6df588f40e | ||
|
|
b46ed58dff | ||
|
|
0d9f57a9c9 | ||
|
|
b7dbe54c83 | ||
|
|
43d1dfff71 | ||
|
|
9949a46ee3 | ||
|
|
dfbcf05b2f | ||
|
|
f56fab9876 | ||
|
|
c9ea7da092 | ||
|
|
22d46e1e90 | ||
|
|
6af94afc56 | ||
|
|
f7f92903de | ||
|
|
b35e17526b | ||
|
|
bf32c08d37 | ||
|
|
5d337409d6 |
5
.github/workflows/ci.yaml
vendored
5
.github/workflows/ci.yaml
vendored
@@ -24,4 +24,7 @@ jobs:
|
|||||||
run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test:ci
|
||||||
2
.github/workflows/cla.yaml
vendored
2
.github/workflows/cla.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
contents: write # this can be 'read' if the signatures are in remote repository
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
statuses: write
|
statuses: write
|
||||||
|
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,13 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Run linting first
|
|
||||||
npm run lint || { echo "lint failed, please run \"npm run lint:fix\" to fix the errors." ; exit 1; }
|
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
|
|
||||||
|
|||||||
@@ -1,214 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
{
|
|
||||||
"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": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
180
CHANGELOG.md
180
CHANGELOG.md
@@ -1,5 +1,185 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.17.0](https://github.com/chartdb/chartdb/compare/v1.16.0...v1.17.0) (2025-10-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* create relationships on canvas modal ([#946](https://github.com/chartdb/chartdb/issues/946)) ([34475ad](https://github.com/chartdb/chartdb/commit/34475add32f11323589ef092ccf2a8e9152ff272))
|
||||||
|
|
||||||
|
|
||||||
|
### 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 open table in editor from canvas edit ([#952](https://github.com/chartdb/chartdb/issues/952)) ([7d811de](https://github.com/chartdb/chartdb/commit/7d811de097eb11e51012772fa6bf586fd0b16c62))
|
||||||
|
* add rels export dbml ([#937](https://github.com/chartdb/chartdb/issues/937)) ([c3c646b](https://github.com/chartdb/chartdb/commit/c3c646bf7cbb1328f4b2eb85c9a7e929f0fcd3b9))
|
||||||
|
* add support for arrays ([#949](https://github.com/chartdb/chartdb/issues/949)) ([49328d8](https://github.com/chartdb/chartdb/commit/49328d8fbd7786f6c0c04cd5605d43a24cbf10ea))
|
||||||
|
* add support for parsing default values in DBML ([#948](https://github.com/chartdb/chartdb/issues/948)) ([459698b](https://github.com/chartdb/chartdb/commit/459698b5d0a1ff23a3719c2e55e4ab2e2384c4fe))
|
||||||
|
* add timestampz and int as datatypes to postgres ([#940](https://github.com/chartdb/chartdb/issues/940)) ([b15bc94](https://github.com/chartdb/chartdb/commit/b15bc945acb96d7cb3832b3b1b607dfcaef9e5ca))
|
||||||
|
* auto-enter edit mode when creating new tables from canvas ([#943](https://github.com/chartdb/chartdb/issues/943)) ([bcd8aa9](https://github.com/chartdb/chartdb/commit/bcd8aa9378aa563f40a2b6802cc503be4c882356))
|
||||||
|
* dbml diff fields types preview ([#934](https://github.com/chartdb/chartdb/issues/934)) ([bb03309](https://github.com/chartdb/chartdb/commit/bb033091b1f64b888822be1423a80f16f5314f6b))
|
||||||
|
* exit table edit on area click ([#945](https://github.com/chartdb/chartdb/issues/945)) ([38fedce](https://github.com/chartdb/chartdb/commit/38fedcec0c10ea2b3f0b7fc92ca1f5ac9e540389))
|
||||||
|
* import array fields ([#961](https://github.com/chartdb/chartdb/issues/961)) ([91e713c](https://github.com/chartdb/chartdb/commit/91e713c30a44f1ba7a767ca7816079610136fcb8))
|
||||||
|
* manipulate schema directly from the canvas ([#947](https://github.com/chartdb/chartdb/issues/947)) ([7ad0e77](https://github.com/chartdb/chartdb/commit/7ad0e7712de975a23b2a337dc0a4a7fb4b122bd1))
|
||||||
|
* preserve multi-word types in DBML export/import ([#956](https://github.com/chartdb/chartdb/issues/956)) ([9ed27cf](https://github.com/chartdb/chartdb/commit/9ed27cf30cca1312713e80e525138f0c27154936))
|
||||||
|
* prevent text input glitch when editing table field names ([#944](https://github.com/chartdb/chartdb/issues/944)) ([498655e](https://github.com/chartdb/chartdb/commit/498655e7b77e57eaf641ba86263ce1ef60b93e16))
|
||||||
|
* resolve canvas filter tree state issues ([#953](https://github.com/chartdb/chartdb/issues/953)) ([ccb29e0](https://github.com/chartdb/chartdb/commit/ccb29e0a574dfa4cfdf0ebf242a4c4aaa48cc37b))
|
||||||
|
* resolve dbml increment & nullable attributes issue ([#954](https://github.com/chartdb/chartdb/issues/954)) ([2c4b344](https://github.com/chartdb/chartdb/commit/2c4b344efb24041e7f607fc6124e109b69aaa457))
|
||||||
|
* show SQL Script option conditionally for databases without DDL support ([#960](https://github.com/chartdb/chartdb/issues/960)) ([acf6d4b](https://github.com/chartdb/chartdb/commit/acf6d4b3654d8868b8a8ebf717c608d9749b71da))
|
||||||
|
* use flag for custom types ([#951](https://github.com/chartdb/chartdb/issues/951)) ([62dec48](https://github.com/chartdb/chartdb/commit/62dec4857211b705a8039691da1772263ea986fe))
|
||||||
|
|
||||||
|
## [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)
|
## [1.13.2](https://github.com/chartdb/chartdb/compare/v1.13.1...v1.13.2) (2025-07-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
index.html
14
index.html
@@ -4,8 +4,9 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="robots" content="max-image-preview:large" />
|
<meta name="robots" content="noindex, max-image-preview:large" />
|
||||||
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
|
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
|
||||||
|
<link rel="canonical" href="https://chartdb.io" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
@@ -15,14 +16,19 @@
|
|||||||
<script src="/config.js"></script>
|
<script src="/config.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Load analytics only if not disabled
|
// Load analytics only if not disabled
|
||||||
(function() {
|
(function () {
|
||||||
const disableAnalytics = (window.env && window.env.DISABLE_ANALYTICS === 'true') ||
|
const disableAnalytics =
|
||||||
(typeof process !== 'undefined' && process.env && process.env.VITE_DISABLE_ANALYTICS === 'true');
|
(window.env && window.env.DISABLE_ANALYTICS === 'true') ||
|
||||||
|
(typeof process !== 'undefined' &&
|
||||||
|
process.env &&
|
||||||
|
process.env.VITE_DISABLE_ANALYTICS === 'true');
|
||||||
|
|
||||||
if (!disableAnalytics) {
|
if (!disableAnalytics) {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = 'https://cdn.usefathom.com/script.js';
|
script.src = 'https://cdn.usefathom.com/script.js';
|
||||||
script.setAttribute('data-site', 'PRHIVBNN');
|
script.setAttribute('data-site', 'PRHIVBNN');
|
||||||
|
script.setAttribute('data-canonical', 'false');
|
||||||
|
script.setAttribute('data-spa', 'auto');
|
||||||
script.defer = true;
|
script.defer = true;
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
}
|
||||||
|
|||||||
2633
package-lock.json
generated
2633
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.13.2",
|
"version": "1.17.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -11,12 +11,13 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
|
"test:ci": "vitest run --reporter=verbose --bail=1",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
"test:coverage": "vitest --coverage"
|
"test:coverage": "vitest --coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.51",
|
"@ai-sdk/openai": "^0.0.51",
|
||||||
"@dbml/core": "^3.9.5",
|
"@dbml/core": "^3.13.9",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
@@ -25,24 +26,24 @@
|
|||||||
"@radix-ui/react-checkbox": "^1.1.1",
|
"@radix-ui/react-checkbox": "^1.1.1",
|
||||||
"@radix-ui/react-collapsible": "^1.1.0",
|
"@radix-ui/react-collapsible": "^1.1.0",
|
||||||
"@radix-ui/react-context-menu": "^2.2.1",
|
"@radix-ui/react-context-menu": "^2.2.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-hover-card": "^1.1.1",
|
"@radix-ui/react-hover-card": "^1.1.1",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-menubar": "^1.1.1",
|
"@radix-ui/react-menubar": "^1.1.1",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
"@radix-ui/react-scroll-area": "1.2.0",
|
"@radix-ui/react-scroll-area": "1.2.0",
|
||||||
"@radix-ui/react-select": "^2.1.1",
|
"@radix-ui/react-select": "^2.1.1",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.0",
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.2.7",
|
||||||
"@uidotdev/usehooks": "^2.4.1",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"@xyflow/react": "^12.3.1",
|
"@xyflow/react": "^12.8.2",
|
||||||
"ahooks": "^3.8.1",
|
"ahooks": "^3.8.1",
|
||||||
"ai": "^3.3.14",
|
"ai": "^3.3.14",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -53,8 +54,9 @@
|
|||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^23.14.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"lucide-react": "^0.441.0",
|
"lucide-react": "^0.525.0",
|
||||||
"monaco-editor": "^0.52.0",
|
"monaco-editor": "^0.52.0",
|
||||||
|
"motion": "^12.23.6",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"node-sql-parser": "^5.3.2",
|
"node-sql-parser": "^5.3.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -62,10 +64,13 @@
|
|||||||
"react-helmet-async": "^2.0.5",
|
"react-helmet-async": "^2.0.5",
|
||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"react-resizable-panels": "^2.0.22",
|
"react-resizable-panels": "^2.0.22",
|
||||||
"react-responsive": "^10.0.0",
|
"react-responsive": "^10.0.0",
|
||||||
"react-router-dom": "^7.1.1",
|
"react-router-dom": "^7.1.1",
|
||||||
"react-use": "^17.5.1",
|
"react-use": "^17.5.1",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
|
"remark-gfm": "^4.0.1",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"timeago-react": "^3.0.6",
|
"timeago-react": "^3.0.6",
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Disallow: /
|
||||||
|
|
||||||
Sitemap: https://app.chartdb.io/sitemap.xml
|
Sitemap: https://app.chartdb.io/sitemap.xml
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cva } from 'class-variance-authority';
|
import { cva } from 'class-variance-authority';
|
||||||
|
|
||||||
export const buttonVariants = cva(
|
export const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
137
src/components/button/button-with-alternatives.tsx
Normal file
137
src/components/button/button-with-alternatives.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ChevronDownIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { buttonVariants } from './button-variants';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/dropdown-menu/dropdown-menu';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/tooltip/tooltip';
|
||||||
|
|
||||||
|
export interface ButtonAlternative {
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonWithAlternativesProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean;
|
||||||
|
alternatives: Array<ButtonAlternative>;
|
||||||
|
dropdownTriggerClassName?: string;
|
||||||
|
chevronDownIconClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonWithAlternatives = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ButtonWithAlternativesProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
alternatives,
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
dropdownTriggerClassName,
|
||||||
|
chevronDownIconClassName,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const Comp = asChild ? Slot : 'button';
|
||||||
|
const hasAlternatives = (alternatives?.length ?? 0) > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="inline-flex items-stretch">
|
||||||
|
<Comp
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant, size }),
|
||||||
|
{ 'rounded-r-none': hasAlternatives },
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
onClick={onClick}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
{hasAlternatives ? (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant, size }),
|
||||||
|
'rounded-l-none border-l border-l-primary/5 px-2 min-w-0',
|
||||||
|
className?.includes('h-') &&
|
||||||
|
className.match(/h-\d+/)?.[0],
|
||||||
|
className?.includes('text-') &&
|
||||||
|
className.match(/text-\w+/)?.[0],
|
||||||
|
dropdownTriggerClassName
|
||||||
|
)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<ChevronDownIcon
|
||||||
|
className={cn(
|
||||||
|
'size-4 shrink-0',
|
||||||
|
chevronDownIconClassName
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{alternatives.map((alternative, index) => {
|
||||||
|
const menuItem = (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={index}
|
||||||
|
onClick={alternative.onClick}
|
||||||
|
disabled={alternative.disabled}
|
||||||
|
className={cn(alternative.className)}
|
||||||
|
>
|
||||||
|
<span className="flex w-full items-center justify-between gap-2">
|
||||||
|
{alternative.label}
|
||||||
|
{alternative.icon}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (alternative.tooltip) {
|
||||||
|
return (
|
||||||
|
<Tooltip key={index}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
{menuItem}
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="left">
|
||||||
|
{alternative.tooltip}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return menuItem;
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ButtonWithAlternatives.displayName = 'ButtonWithAlternatives';
|
||||||
|
|
||||||
|
export { ButtonWithAlternatives };
|
||||||
@@ -31,18 +31,21 @@ export interface CodeSnippetAction {
|
|||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CodeSnippetProps {
|
export interface CodeSnippetProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
code: string;
|
code: string;
|
||||||
codeToCopy?: string;
|
codeToCopy?: string;
|
||||||
language?: 'sql' | 'shell';
|
language?: 'sql' | 'shell' | 'dbml';
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
autoScroll?: boolean;
|
autoScroll?: boolean;
|
||||||
isComplete?: boolean;
|
isComplete?: boolean;
|
||||||
editorProps?: React.ComponentProps<EditorType>;
|
editorProps?: React.ComponentProps<EditorType>;
|
||||||
actions?: CodeSnippetAction[];
|
actions?: CodeSnippetAction[];
|
||||||
|
actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left';
|
||||||
|
allowCopy?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||||
@@ -56,6 +59,8 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
isComplete = true,
|
isComplete = true,
|
||||||
editorProps,
|
editorProps,
|
||||||
actions,
|
actions,
|
||||||
|
actionsTooltipSide,
|
||||||
|
allowCopy = true,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const monaco = useMonaco();
|
const monaco = useMonaco();
|
||||||
@@ -129,33 +134,37 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
<Suspense fallback={<Spinner />}>
|
<Suspense fallback={<Spinner />}>
|
||||||
{isComplete ? (
|
{isComplete ? (
|
||||||
<div className="absolute right-1 top-1 z-10 flex flex-col gap-1">
|
<div className="absolute right-1 top-1 z-10 flex flex-col gap-1">
|
||||||
<Tooltip
|
{allowCopy ? (
|
||||||
onOpenChange={setTooltipOpen}
|
<Tooltip
|
||||||
open={isCopied || tooltipOpen}
|
onOpenChange={setTooltipOpen}
|
||||||
>
|
open={isCopied || tooltipOpen}
|
||||||
<TooltipTrigger asChild>
|
>
|
||||||
<span>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<span>
|
||||||
className="h-fit p-1.5"
|
<Button
|
||||||
variant="outline"
|
className="h-fit p-1.5"
|
||||||
onClick={copyToClipboard}
|
variant="outline"
|
||||||
>
|
onClick={copyToClipboard}
|
||||||
{isCopied ? (
|
>
|
||||||
<CopyCheck size={16} />
|
{isCopied ? (
|
||||||
) : (
|
<CopyCheck size={16} />
|
||||||
<Copy size={16} />
|
) : (
|
||||||
)}
|
<Copy size={16} />
|
||||||
</Button>
|
)}
|
||||||
</span>
|
</Button>
|
||||||
</TooltipTrigger>
|
</span>
|
||||||
<TooltipContent>
|
</TooltipTrigger>
|
||||||
{t(
|
<TooltipContent
|
||||||
isCopied
|
side={actionsTooltipSide}
|
||||||
? 'copied'
|
>
|
||||||
: 'copy_to_clipboard'
|
{t(
|
||||||
)}
|
isCopied
|
||||||
</TooltipContent>
|
? 'copied'
|
||||||
</Tooltip>
|
: 'copy_to_clipboard'
|
||||||
|
)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{actions &&
|
{actions &&
|
||||||
actions.length > 0 &&
|
actions.length > 0 &&
|
||||||
@@ -164,7 +173,10 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<span>
|
<span>
|
||||||
<Button
|
<Button
|
||||||
className="h-fit p-1.5"
|
className={cn(
|
||||||
|
'h-fit p-1.5',
|
||||||
|
action.className
|
||||||
|
)}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
>
|
>
|
||||||
@@ -174,7 +186,9 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
|||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent
|
||||||
|
side={actionsTooltipSide}
|
||||||
|
>
|
||||||
{action.label}
|
{action.label}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
51
src/components/code-snippet/dbml/utils.ts
Normal file
51
src/components/code-snippet/dbml/utils.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import type { DBMLError } from '@/lib/dbml/dbml-import/dbml-import-error';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
|
||||||
|
export const highlightErrorLine = ({
|
||||||
|
error,
|
||||||
|
model,
|
||||||
|
editorDecorationsCollection,
|
||||||
|
}: {
|
||||||
|
error: DBMLError;
|
||||||
|
model?: monaco.editor.ITextModel | null;
|
||||||
|
editorDecorationsCollection:
|
||||||
|
| monaco.editor.IEditorDecorationsCollection
|
||||||
|
| undefined;
|
||||||
|
}) => {
|
||||||
|
if (!model) return;
|
||||||
|
if (!editorDecorationsCollection) return;
|
||||||
|
|
||||||
|
const decorations = [
|
||||||
|
{
|
||||||
|
range: new monaco.Range(
|
||||||
|
error.line,
|
||||||
|
1,
|
||||||
|
error.line,
|
||||||
|
model.getLineMaxColumn(error.line)
|
||||||
|
),
|
||||||
|
options: {
|
||||||
|
isWholeLine: true,
|
||||||
|
className: 'dbml-error-line',
|
||||||
|
glyphMarginClassName: 'dbml-error-glyph',
|
||||||
|
hoverMessage: { value: error.message },
|
||||||
|
overviewRuler: {
|
||||||
|
color: '#ff0000',
|
||||||
|
position: monaco.editor.OverviewRulerLane.Right,
|
||||||
|
darkColor: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
editorDecorationsCollection?.set(decorations);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearErrorHighlight = (
|
||||||
|
editorDecorationsCollection:
|
||||||
|
| monaco.editor.IEditorDecorationsCollection
|
||||||
|
| undefined
|
||||||
|
) => {
|
||||||
|
if (editorDecorationsCollection) {
|
||||||
|
editorDecorationsCollection.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -9,12 +9,14 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
|
|||||||
base: 'vs-dark',
|
base: 'vs-dark',
|
||||||
inherit: true,
|
inherit: true,
|
||||||
rules: [
|
rules: [
|
||||||
|
{ token: 'comment', foreground: '6A9955' }, // Comments
|
||||||
{ token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords
|
{ token: 'keyword', foreground: '569CD6' }, // Table, Ref keywords
|
||||||
{ token: 'string', foreground: 'CE9178' }, // Strings
|
{ token: 'string', foreground: 'CE9178' }, // Strings
|
||||||
{ token: 'annotation', foreground: '9CDCFE' }, // [annotations]
|
{ token: 'annotation', foreground: '9CDCFE' }, // [annotations]
|
||||||
{ token: 'delimiter', foreground: 'D4D4D4' }, // Braces {}
|
{ token: 'delimiter', foreground: 'D4D4D4' }, // Braces {}
|
||||||
{ token: 'operator', foreground: 'D4D4D4' }, // Operators
|
{ token: 'operator', foreground: 'D4D4D4' }, // Operators
|
||||||
{ token: 'datatype', foreground: '4EC9B0' }, // Data types
|
{ token: 'type', foreground: '4EC9B0' }, // Data types
|
||||||
|
{ token: 'identifier', foreground: '9CDCFE' }, // Field names
|
||||||
],
|
],
|
||||||
colors: {},
|
colors: {},
|
||||||
});
|
});
|
||||||
@@ -23,12 +25,14 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
|
|||||||
base: 'vs',
|
base: 'vs',
|
||||||
inherit: true,
|
inherit: true,
|
||||||
rules: [
|
rules: [
|
||||||
|
{ token: 'comment', foreground: '008000' }, // Comments
|
||||||
{ token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords
|
{ token: 'keyword', foreground: '0000FF' }, // Table, Ref keywords
|
||||||
{ token: 'string', foreground: 'A31515' }, // Strings
|
{ token: 'string', foreground: 'A31515' }, // Strings
|
||||||
{ token: 'annotation', foreground: '001080' }, // [annotations]
|
{ token: 'annotation', foreground: '001080' }, // [annotations]
|
||||||
{ token: 'delimiter', foreground: '000000' }, // Braces {}
|
{ token: 'delimiter', foreground: '000000' }, // Braces {}
|
||||||
{ token: 'operator', foreground: '000000' }, // Operators
|
{ token: 'operator', foreground: '000000' }, // Operators
|
||||||
{ token: 'type', foreground: '267F99' }, // Data types
|
{ token: 'type', foreground: '267F99' }, // Data types
|
||||||
|
{ token: 'identifier', foreground: '001080' }, // Field names
|
||||||
],
|
],
|
||||||
colors: {},
|
colors: {},
|
||||||
});
|
});
|
||||||
@@ -37,17 +41,63 @@ export const setupDBMLLanguage = (monaco: Monaco) => {
|
|||||||
const datatypePattern = dataTypesNames.join('|');
|
const datatypePattern = dataTypesNames.join('|');
|
||||||
|
|
||||||
monaco.languages.setMonarchTokensProvider('dbml', {
|
monaco.languages.setMonarchTokensProvider('dbml', {
|
||||||
keywords: ['Table', 'Ref', 'Indexes'],
|
keywords: ['Table', 'Ref', 'Indexes', 'Note', 'Enum', 'enum'],
|
||||||
datatypes: dataTypesNames,
|
datatypes: dataTypesNames,
|
||||||
|
operators: ['>', '<', '-'],
|
||||||
|
|
||||||
tokenizer: {
|
tokenizer: {
|
||||||
root: [
|
root: [
|
||||||
[/\b(Table|Ref|Indexes)\b/, 'keyword'],
|
// 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
|
||||||
[/\[.*?\]/, 'annotation'],
|
[/\[.*?\]/, 'annotation'],
|
||||||
[/".*?"/, 'string'],
|
|
||||||
[/'.*?'/, 'string'],
|
// Strings
|
||||||
[/[{}]/, 'delimiter'],
|
[/'''/, 'string', '@tripleQuoteString'],
|
||||||
[/[<>]/, 'operator'],
|
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-terminated string
|
||||||
[new RegExp(`\\b(${datatypePattern})\\b`, 'i'), 'type'], // Added 'i' flag for case-insensitive matching
|
[/'([^'\\]|\\.)*$/, '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'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,27 +5,45 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/popover/popover';
|
} from '@/components/popover/popover';
|
||||||
import { colorOptions } from '@/lib/colors';
|
import { colorOptions } from '@/lib/colors';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export interface ColorPickerProps {
|
export interface ColorPickerProps {
|
||||||
color: string;
|
color: string;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
popoverOnMouseDown?: (e: React.MouseEvent) => void;
|
||||||
|
popoverOnClick?: (e: React.MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ColorPicker = React.forwardRef<
|
export const ColorPicker = React.forwardRef<
|
||||||
React.ElementRef<typeof PopoverTrigger>,
|
React.ElementRef<typeof PopoverTrigger>,
|
||||||
ColorPickerProps
|
ColorPickerProps
|
||||||
>(({ color, onChange }, ref) => {
|
>(({ color, onChange, disabled, popoverOnMouseDown, popoverOnClick }, ref) => {
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild ref={ref}>
|
<PopoverTrigger
|
||||||
|
asChild
|
||||||
|
ref={ref}
|
||||||
|
disabled={disabled}
|
||||||
|
{...(disabled ? { onClick: (e) => e.preventDefault() } : {})}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
|
className={cn(
|
||||||
|
'h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md',
|
||||||
|
{
|
||||||
|
'hover:shadow-none cursor-default': disabled,
|
||||||
|
}
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-fit">
|
<PopoverContent
|
||||||
|
className="w-fit"
|
||||||
|
onMouseDown={popoverOnMouseDown}
|
||||||
|
onClick={popoverOnClick}
|
||||||
|
>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
{colorOptions.map((option) => (
|
{colorOptions.map((option) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Cross2Icon } from '@radix-ui/react-icons';
|
|||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { ScrollArea } from '../scroll-area/scroll-area';
|
import { ScrollArea } from '../scroll-area/scroll-area';
|
||||||
|
import { ChevronLeft } from 'lucide-react';
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root;
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
@@ -32,28 +33,75 @@ const DialogContent = React.forwardRef<
|
|||||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||||
showClose?: boolean;
|
showClose?: boolean;
|
||||||
|
showBack?: boolean;
|
||||||
|
backButtonClassName?: string;
|
||||||
|
blurBackground?: boolean;
|
||||||
|
forceOverlay?: boolean;
|
||||||
|
onBackClick?: () => void;
|
||||||
}
|
}
|
||||||
>(({ className, children, showClose, ...props }, ref) => (
|
>(
|
||||||
<DialogPortal>
|
(
|
||||||
<DialogOverlay />
|
{
|
||||||
<DialogPrimitive.Content
|
className,
|
||||||
ref={ref}
|
children,
|
||||||
className={cn(
|
showClose,
|
||||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
showBack,
|
||||||
className
|
onBackClick,
|
||||||
)}
|
backButtonClassName,
|
||||||
{...props}
|
blurBackground,
|
||||||
>
|
forceOverlay,
|
||||||
{children}
|
...props
|
||||||
{showClose && (
|
},
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
ref
|
||||||
<Cross2Icon className="size-4" />
|
) => (
|
||||||
<span className="sr-only">Close</span>
|
<DialogPortal>
|
||||||
</DialogPrimitive.Close>
|
{forceOverlay ? (
|
||||||
)}
|
<div
|
||||||
</DialogPrimitive.Content>
|
className={cn(
|
||||||
</DialogPortal>
|
'fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
));
|
{
|
||||||
|
'bg-black/80': !blurBackground,
|
||||||
|
'bg-black/30 backdrop-blur-sm': blurBackground,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
data-state="open"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<DialogOverlay
|
||||||
|
className={cn({
|
||||||
|
'bg-black/30 backdrop-blur-sm': blurBackground,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showBack && (
|
||||||
|
<button
|
||||||
|
onClick={() => onBackClick?.()}
|
||||||
|
className={cn(
|
||||||
|
'absolute left-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground',
|
||||||
|
backButtonClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="size-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{showClose && (
|
||||||
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<Cross2Icon className="size-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
)
|
||||||
|
);
|
||||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
const DialogHeader = ({
|
const DialogHeader = ({
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import React, { forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
import EmptyStateImage from '@/assets/empty_state.png';
|
import EmptyStateImage from '@/assets/empty_state.png';
|
||||||
import EmptyStateImageDark from '@/assets/empty_state_dark.png';
|
import EmptyStateImageDark from '@/assets/empty_state_dark.png';
|
||||||
import { Label } from '@/components/label/label';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
|
import {
|
||||||
|
Empty,
|
||||||
|
EmptyContent,
|
||||||
|
EmptyDescription,
|
||||||
|
EmptyHeader,
|
||||||
|
EmptyMedia,
|
||||||
|
EmptyTitle,
|
||||||
|
} from '../empty/empty';
|
||||||
|
|
||||||
export interface EmptyStateProps {
|
export interface EmptyStateProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -38,26 +45,29 @@ export const EmptyState = forwardRef<
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<img
|
<Empty>
|
||||||
src={
|
<EmptyHeader>
|
||||||
effectiveTheme === 'dark'
|
<EmptyMedia variant="icon">
|
||||||
? EmptyStateImageDark
|
{/* <Group /> */}
|
||||||
: EmptyStateImage
|
<img
|
||||||
}
|
src={
|
||||||
alt="Empty state"
|
effectiveTheme === 'dark'
|
||||||
className={cn('mb-2 w-20', imageClassName)}
|
? EmptyStateImageDark
|
||||||
/>
|
: EmptyStateImage
|
||||||
<Label className={cn('text-base', titleClassName)}>
|
}
|
||||||
{title}
|
alt="Empty state"
|
||||||
</Label>
|
className={cn('p-2', imageClassName)}
|
||||||
<Label
|
/>
|
||||||
className={cn(
|
</EmptyMedia>
|
||||||
'text-sm font-normal text-muted-foreground',
|
<EmptyTitle className={titleClassName}>
|
||||||
descriptionClassName
|
{title}
|
||||||
)}
|
</EmptyTitle>
|
||||||
>
|
<EmptyDescription className={descriptionClassName}>
|
||||||
{description}
|
{description}
|
||||||
</Label>
|
</EmptyDescription>
|
||||||
|
</EmptyHeader>
|
||||||
|
<EmptyContent />
|
||||||
|
</Empty>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
105
src/components/empty/empty.tsx
Normal file
105
src/components/empty/empty.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils/index';
|
||||||
|
|
||||||
|
function Empty({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="empty"
|
||||||
|
className={cn(
|
||||||
|
'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="empty-header"
|
||||||
|
className={cn(
|
||||||
|
'flex max-w-sm flex-col items-center gap-2 text-center',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyMediaVariants = cva(
|
||||||
|
'mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-transparent',
|
||||||
|
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function EmptyMedia({
|
||||||
|
className,
|
||||||
|
variant = 'default',
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="empty-icon"
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(emptyMediaVariants({ variant, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="empty-title"
|
||||||
|
className={cn('text-lg font-medium tracking-tight', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="empty-description"
|
||||||
|
className={cn(
|
||||||
|
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="empty-content"
|
||||||
|
className={cn(
|
||||||
|
'flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Empty,
|
||||||
|
EmptyHeader,
|
||||||
|
EmptyTitle,
|
||||||
|
EmptyDescription,
|
||||||
|
EmptyContent,
|
||||||
|
EmptyMedia,
|
||||||
|
};
|
||||||
@@ -2,16 +2,13 @@ import React from 'react';
|
|||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export interface InputProps
|
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
121
src/components/pagination/pagination.tsx
Normal file
121
src/components/pagination/pagination.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import type { ButtonProps } from '../button/button';
|
||||||
|
import { buttonVariants } from '../button/button-variants';
|
||||||
|
import {
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
DotsHorizontalIcon,
|
||||||
|
} from '@radix-ui/react-icons';
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn('mx-auto flex w-full justify-center', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Pagination.displayName = 'Pagination';
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<'ul'>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn('flex flex-row items-center gap-1', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
PaginationContent.displayName = 'PaginationContent';
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<'li'>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn('', className)} {...props} />
|
||||||
|
));
|
||||||
|
PaginationItem.displayName = 'PaginationItem';
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean;
|
||||||
|
} & Pick<ButtonProps, 'size'> &
|
||||||
|
React.ComponentProps<'a'>;
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = 'icon',
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? 'page' : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? 'outline' : 'ghost',
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
PaginationLink.displayName = 'PaginationLink';
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn('gap-1 pl-2.5', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className="size-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
);
|
||||||
|
PaginationPrevious.displayName = 'PaginationPrevious';
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn('gap-1 pr-2.5', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRightIcon className="size-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
);
|
||||||
|
PaginationNext.displayName = 'PaginationNext';
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'span'>) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn('flex h-9 w-9 items-center justify-center', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<DotsHorizontalIcon className="size-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
PaginationEllipsis.displayName = 'PaginationEllipsis';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationPrevious,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationEllipsis,
|
||||||
|
};
|
||||||
@@ -27,6 +27,7 @@ export interface SelectBoxOption {
|
|||||||
regex?: string;
|
regex?: string;
|
||||||
extractRegex?: RegExp;
|
extractRegex?: RegExp;
|
||||||
group?: string;
|
group?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectBoxProps {
|
export interface SelectBoxProps {
|
||||||
@@ -53,6 +54,11 @@ export interface SelectBoxProps {
|
|||||||
open?: boolean;
|
open?: boolean;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
popoverClassName?: string;
|
popoverClassName?: string;
|
||||||
|
readonly?: boolean;
|
||||||
|
footerButtons?: React.ReactNode;
|
||||||
|
commandOnMouseDown?: (e: React.MouseEvent) => void;
|
||||||
|
commandOnClick?: (e: React.MouseEvent) => void;
|
||||||
|
onSearchChange?: (search: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||||
@@ -78,6 +84,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
open,
|
open,
|
||||||
onOpenChange: setOpen,
|
onOpenChange: setOpen,
|
||||||
popoverClassName,
|
popoverClassName,
|
||||||
|
readonly,
|
||||||
|
footerButtons,
|
||||||
|
commandOnMouseDown,
|
||||||
|
commandOnClick,
|
||||||
|
onSearchChange,
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
@@ -93,6 +104,12 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
(isOpen: boolean) => {
|
(isOpen: boolean) => {
|
||||||
setOpen?.(isOpen);
|
setOpen?.(isOpen);
|
||||||
setIsOpen(isOpen);
|
setIsOpen(isOpen);
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
setSearchTerm('');
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
|
||||||
},
|
},
|
||||||
[setOpen]
|
[setOpen]
|
||||||
);
|
);
|
||||||
@@ -146,18 +163,20 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`}
|
className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`}
|
||||||
>
|
>
|
||||||
<span>{option.label}</span>
|
<span>{option.label}</span>
|
||||||
<span
|
{!readonly ? (
|
||||||
onClick={(e) => {
|
<span
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
handleSelect(option.value);
|
e.preventDefault();
|
||||||
}}
|
handleSelect(option.value);
|
||||||
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
|
}}
|
||||||
>
|
className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
|
||||||
<Cross2Icon />
|
>
|
||||||
</span>
|
<Cross2Icon />
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
)),
|
)),
|
||||||
[options, value, handleSelect, oneLine, keepOrder]
|
[options, value, handleSelect, oneLine, keepOrder, readonly]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isAllSelected = React.useMemo(
|
const isAllSelected = React.useMemo(
|
||||||
@@ -223,13 +242,16 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
<CommandItem
|
<CommandItem
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
key={option.value}
|
key={option.value}
|
||||||
|
value={option.label}
|
||||||
keywords={option.regex ? [option.regex] : undefined}
|
keywords={option.regex ? [option.regex] : undefined}
|
||||||
onSelect={() =>
|
onSelect={() =>
|
||||||
handleSelect(
|
handleSelect(
|
||||||
option.value,
|
option.value,
|
||||||
matches?.map((match) => match.toString())
|
matches?.map((match) => match?.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
onMouseDown={commandOnMouseDown}
|
||||||
|
onClick={commandOnClick}
|
||||||
>
|
>
|
||||||
{multiple && (
|
{multiple && (
|
||||||
<div
|
<div
|
||||||
@@ -244,6 +266,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-1 items-center truncate">
|
<div className="flex flex-1 items-center truncate">
|
||||||
|
{option.icon ? (
|
||||||
|
<span className="mr-2 shrink-0">
|
||||||
|
{option.icon}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
<span>
|
<span>
|
||||||
{isRegexMatch ? searchTerm : option.label}
|
{isRegexMatch ? searchTerm : option.label}
|
||||||
{!isRegexMatch && optionSuffix
|
{!isRegexMatch && optionSuffix
|
||||||
@@ -270,7 +297,15 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
</CommandItem>
|
</CommandItem>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[value, multiple, searchTerm, handleSelect, optionSuffix]
|
[
|
||||||
|
value,
|
||||||
|
multiple,
|
||||||
|
searchTerm,
|
||||||
|
handleSelect,
|
||||||
|
optionSuffix,
|
||||||
|
commandOnClick,
|
||||||
|
commandOnMouseDown,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -278,7 +313,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
|
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`,
|
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''} ${readonly ? 'pointer-events-none' : ''}`,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -348,6 +383,8 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
popoverClassName
|
popoverClassName
|
||||||
)}
|
)}
|
||||||
align="center"
|
align="center"
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Command
|
<Command
|
||||||
filter={(value, search, keywords) => {
|
filter={(value, search, keywords) => {
|
||||||
@@ -370,7 +407,10 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<CommandInput
|
<CommandInput
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onValueChange={(e) => setSearchTerm(e)}
|
onValueChange={(e) => {
|
||||||
|
setSearchTerm(e);
|
||||||
|
onSearchChange?.(e);
|
||||||
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
placeholder={inputPlaceholder ?? 'Search...'}
|
placeholder={inputPlaceholder ?? 'Search...'}
|
||||||
className="h-9"
|
className="h-9"
|
||||||
@@ -437,6 +477,9 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
|||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Command>
|
</Command>
|
||||||
|
{footerButtons ? (
|
||||||
|
<div className="border-t">{footerButtons}</div>
|
||||||
|
) : null}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|||||||
const SIDEBAR_WIDTH = '16rem';
|
const SIDEBAR_WIDTH = '16rem';
|
||||||
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||||
const SIDEBAR_WIDTH_ICON = '3rem';
|
const SIDEBAR_WIDTH_ICON = '3rem';
|
||||||
|
const SIDEBAR_WIDTH_ICON_EXTENDED = '4rem';
|
||||||
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
||||||
|
|
||||||
type SidebarContext = {
|
type SidebarContext = {
|
||||||
@@ -142,6 +143,8 @@ const SidebarProvider = React.forwardRef<
|
|||||||
{
|
{
|
||||||
'--sidebar-width': SIDEBAR_WIDTH,
|
'--sidebar-width': SIDEBAR_WIDTH,
|
||||||
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
||||||
|
'--sidebar-width-icon-extended':
|
||||||
|
SIDEBAR_WIDTH_ICON_EXTENDED,
|
||||||
...style,
|
...style,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
@@ -166,7 +169,7 @@ const Sidebar = React.forwardRef<
|
|||||||
React.ComponentProps<'div'> & {
|
React.ComponentProps<'div'> & {
|
||||||
side?: 'left' | 'right';
|
side?: 'left' | 'right';
|
||||||
variant?: 'sidebar' | 'floating' | 'inset';
|
variant?: 'sidebar' | 'floating' | 'inset';
|
||||||
collapsible?: 'offcanvas' | 'icon' | 'none';
|
collapsible?: 'offcanvas' | 'icon' | 'icon-extended' | 'none';
|
||||||
}
|
}
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
@@ -245,8 +248,8 @@ const Sidebar = React.forwardRef<
|
|||||||
'group-data-[collapsible=offcanvas]:w-0',
|
'group-data-[collapsible=offcanvas]:w-0',
|
||||||
'group-data-[side=right]:rotate-180',
|
'group-data-[side=right]:rotate-180',
|
||||||
variant === 'floating' || variant === 'inset'
|
variant === 'floating' || variant === 'inset'
|
||||||
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
|
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4))]'
|
||||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
|
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -257,8 +260,8 @@ const Sidebar = React.forwardRef<
|
|||||||
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
||||||
// Adjust the padding for floating and inset variants.
|
// Adjust the padding for floating and inset variants.
|
||||||
variant === 'floating' || variant === 'inset'
|
variant === 'floating' || variant === 'inset'
|
||||||
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
|
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4)_+2px)]'
|
||||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
|
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended] group-data-[side=left]:border-r group-data-[side=right]:border-l',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -421,7 +424,7 @@ const SidebarContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
data-sidebar="content"
|
data-sidebar="content"
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
|
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden group-data-[collapsible=icon-extended]:overflow-hidden',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -461,6 +464,7 @@ const SidebarGroupLabel = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||||
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
||||||
|
'group-data-[collapsible=icon-extended]:-mt-8 group-data-[collapsible=icon-extended]:opacity-0',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -483,7 +487,7 @@ const SidebarGroupAction = React.forwardRef<
|
|||||||
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||||
// Increases the hit area of the button on mobile.
|
// Increases the hit area of the button on mobile.
|
||||||
'after:absolute after:-inset-2 after:md:hidden',
|
'after:absolute after:-inset-2 after:md:hidden',
|
||||||
'group-data-[collapsible=icon]:hidden',
|
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -532,7 +536,7 @@ const SidebarMenuItem = React.forwardRef<
|
|||||||
SidebarMenuItem.displayName = 'SidebarMenuItem';
|
SidebarMenuItem.displayName = 'SidebarMenuItem';
|
||||||
|
|
||||||
const sidebarMenuButtonVariants = cva(
|
const sidebarMenuButtonVariants = cva(
|
||||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon-extended]:h-auto group-data-[collapsible=icon-extended]:flex-col group-data-[collapsible=icon-extended]:gap-1 group-data-[collapsible=icon-extended]:p-2 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate group-data-[collapsible=icon-extended]:[&>span]:w-full group-data-[collapsible=icon-extended]:[&>span]:text-center group-data-[collapsible=icon-extended]:[&>span]:text-[10px] group-data-[collapsible=icon-extended]:[&>span]:leading-tight [&>svg]:size-4 [&>svg]:shrink-0',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -636,7 +640,7 @@ const SidebarMenuAction = React.forwardRef<
|
|||||||
'peer-data-[size=sm]/menu-button:top-1',
|
'peer-data-[size=sm]/menu-button:top-1',
|
||||||
'peer-data-[size=default]/menu-button:top-1.5',
|
'peer-data-[size=default]/menu-button:top-1.5',
|
||||||
'peer-data-[size=lg]/menu-button:top-2.5',
|
'peer-data-[size=lg]/menu-button:top-2.5',
|
||||||
'group-data-[collapsible=icon]:hidden',
|
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||||
showOnHover &&
|
showOnHover &&
|
||||||
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
|
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
|
||||||
className
|
className
|
||||||
@@ -753,7 +757,7 @@ const SidebarMenuSubButton = React.forwardRef<
|
|||||||
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
||||||
size === 'sm' && 'text-xs',
|
size === 'sm' && 'text-xs',
|
||||||
size === 'md' && 'text-sm',
|
size === 'md' && 'text-sm',
|
||||||
'group-data-[collapsible=icon]:hidden',
|
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export function Toaster() {
|
|||||||
description,
|
description,
|
||||||
action,
|
action,
|
||||||
layout = 'row',
|
layout = 'row',
|
||||||
|
hideCloseButton = false,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -38,7 +39,7 @@ export function Toaster() {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{layout === 'row' ? action : null}
|
{layout === 'row' ? action : null}
|
||||||
<ToastClose />
|
{!hideCloseButton ? <ToastClose /> : null}
|
||||||
</Toast>
|
</Toast>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type ToasterToast = ToastProps & {
|
|||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
action?: ToastActionElement;
|
action?: ToastActionElement;
|
||||||
layout?: 'row' | 'column';
|
layout?: 'row' | 'column';
|
||||||
|
hideCloseButton?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
|||||||
@@ -13,15 +13,17 @@ const TooltipContent = React.forwardRef<
|
|||||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
// <TooltipPrimitive.Portal>
|
||||||
<TooltipPrimitive.Content
|
<TooltipPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
// </TooltipPrimitive.Portal>
|
||||||
));
|
));
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
|||||||
17
src/components/tree-view/tree-item-skeleton.tsx
Normal file
17
src/components/tree-view/tree-item-skeleton.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Skeleton } from '../skeleton/skeleton';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export interface TreeItemSkeletonProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement> {}
|
||||||
|
|
||||||
|
export const TreeItemSkeleton: React.FC<TreeItemSkeletonProps> = ({
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('px-2 py-1', className)} style={style}>
|
||||||
|
<Skeleton className="h-3.5 w-full rounded-sm" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
472
src/components/tree-view/tree-view.tsx
Normal file
472
src/components/tree-view/tree-view.tsx
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
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[];
|
||||||
|
disableCache?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
disableCache = false,
|
||||||
|
}: TreeViewProps<Type, Context>) {
|
||||||
|
const { expanded, loading, loadedChildren, hasMoreChildren, toggleNode } =
|
||||||
|
useTree({
|
||||||
|
fetchChildren,
|
||||||
|
expanded: expandedProp,
|
||||||
|
setExpanded: setExpandedProp,
|
||||||
|
disableCache,
|
||||||
|
});
|
||||||
|
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}
|
||||||
|
disableCache={disableCache}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</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[];
|
||||||
|
disableCache?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
disableCache = false,
|
||||||
|
}: TreeNodeProps<Type, Context>) {
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const isExpanded = expanded[node.id];
|
||||||
|
const isLoading = loading[node.id];
|
||||||
|
// If cache is disabled, always use fresh node.children
|
||||||
|
// Otherwise, use cached loadedChildren if available (for async fetched data)
|
||||||
|
const children = disableCache
|
||||||
|
? node.children
|
||||||
|
: node.children || loadedChildren[node.id];
|
||||||
|
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}
|
||||||
|
disableCache={disableCache}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{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 };
|
||||||
|
}
|
||||||
41
src/components/tree-view/tree.ts
Normal file
41
src/components/tree-view/tree.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { LucideIcon } from 'lucide-react';
|
||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
export interface TreeNode<
|
||||||
|
Type extends string,
|
||||||
|
Context extends Record<Type, unknown>,
|
||||||
|
> {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isFolder?: boolean;
|
||||||
|
children?: TreeNode<Type, Context>[];
|
||||||
|
icon?: LucideIcon;
|
||||||
|
iconProps?: React.ComponentProps<LucideIcon>;
|
||||||
|
labelProps?: React.ComponentProps<'span'>;
|
||||||
|
type: Type;
|
||||||
|
unselectable?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
|
context: Context[Type];
|
||||||
|
empty?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FetchChildrenFunction<
|
||||||
|
Type extends string,
|
||||||
|
Context extends Record<Type, unknown>,
|
||||||
|
> = (
|
||||||
|
nodeId: string,
|
||||||
|
nodeType: Type,
|
||||||
|
nodeContext: Context[Type]
|
||||||
|
) => Promise<TreeNode<Type, Context>[]>;
|
||||||
|
|
||||||
|
export interface SelectableTreeProps<
|
||||||
|
Type extends string,
|
||||||
|
Context extends Record<Type, unknown>,
|
||||||
|
> {
|
||||||
|
enabled: boolean;
|
||||||
|
defaultSelectedId?: string;
|
||||||
|
onSelectedChange?: (node: TreeNode<Type, Context>) => void;
|
||||||
|
selectedId?: string;
|
||||||
|
setSelectedId?: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||||
|
}
|
||||||
162
src/components/tree-view/use-tree.ts
Normal file
162
src/components/tree-view/use-tree.ts
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
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,
|
||||||
|
disableCache = false,
|
||||||
|
}: {
|
||||||
|
fetchChildren?: FetchChildrenFunction<Type, Context>;
|
||||||
|
expanded?: ExpandedState;
|
||||||
|
setExpanded?: Dispatch<SetStateAction<ExpandedState>>;
|
||||||
|
disableCache?: boolean;
|
||||||
|
}) {
|
||||||
|
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] || [];
|
||||||
|
|
||||||
|
// Only cache if caching is enabled
|
||||||
|
if (!disableCache && 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 and cache is enabled
|
||||||
|
if (!disableCache && !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,
|
||||||
|
disableCache,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
expanded,
|
||||||
|
loading,
|
||||||
|
loadedChildren,
|
||||||
|
hasMoreChildren,
|
||||||
|
toggleNode,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,6 +2,24 @@ import { createContext } from 'react';
|
|||||||
import { emptyFn } from '@/lib/utils';
|
import { emptyFn } from '@/lib/utils';
|
||||||
import type { Graph } from '@/lib/graph';
|
import type { Graph } from '@/lib/graph';
|
||||||
import { createGraph } from '@/lib/graph';
|
import { createGraph } from '@/lib/graph';
|
||||||
|
import { EventEmitter } from 'ahooks/lib/useEventEmitter';
|
||||||
|
|
||||||
|
export type CanvasEventType = 'pan_click';
|
||||||
|
|
||||||
|
export type CanvasEventBase<T extends CanvasEventType, D> = {
|
||||||
|
action: T;
|
||||||
|
data: D;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PanClickEvent = CanvasEventBase<
|
||||||
|
'pan_click',
|
||||||
|
{
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CanvasEvent = PanClickEvent;
|
||||||
|
|
||||||
export interface CanvasContext {
|
export interface CanvasContext {
|
||||||
reorderTables: (options?: { updateHistory?: boolean }) => void;
|
reorderTables: (options?: { updateHistory?: boolean }) => void;
|
||||||
@@ -12,6 +30,44 @@ export interface CanvasContext {
|
|||||||
}) => void;
|
}) => void;
|
||||||
setOverlapGraph: (graph: Graph<string>) => void;
|
setOverlapGraph: (graph: Graph<string>) => void;
|
||||||
overlapGraph: Graph<string>;
|
overlapGraph: Graph<string>;
|
||||||
|
setShowFilter: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
showFilter: boolean;
|
||||||
|
editTableModeTable: {
|
||||||
|
tableId: string;
|
||||||
|
fieldId?: string;
|
||||||
|
} | null;
|
||||||
|
setEditTableModeTable: React.Dispatch<
|
||||||
|
React.SetStateAction<{
|
||||||
|
tableId: string;
|
||||||
|
fieldId?: string;
|
||||||
|
} | null>
|
||||||
|
>;
|
||||||
|
tempFloatingEdge: {
|
||||||
|
sourceNodeId: string;
|
||||||
|
targetNodeId?: string;
|
||||||
|
} | null;
|
||||||
|
setTempFloatingEdge: React.Dispatch<
|
||||||
|
React.SetStateAction<{
|
||||||
|
sourceNodeId: string;
|
||||||
|
targetNodeId?: string;
|
||||||
|
} | null>
|
||||||
|
>;
|
||||||
|
startFloatingEdgeCreation: ({
|
||||||
|
sourceNodeId,
|
||||||
|
}: {
|
||||||
|
sourceNodeId: string;
|
||||||
|
}) => void;
|
||||||
|
endFloatingEdgeCreation: () => void;
|
||||||
|
hoveringTableId: string | null;
|
||||||
|
setHoveringTableId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
|
showCreateRelationshipNode: (params: {
|
||||||
|
sourceTableId: string;
|
||||||
|
targetTableId: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}) => void;
|
||||||
|
hideCreateRelationshipNode: () => void;
|
||||||
|
events: EventEmitter<CanvasEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const canvasContext = createContext<CanvasContext>({
|
export const canvasContext = createContext<CanvasContext>({
|
||||||
@@ -19,4 +75,17 @@ export const canvasContext = createContext<CanvasContext>({
|
|||||||
fitView: emptyFn,
|
fitView: emptyFn,
|
||||||
setOverlapGraph: emptyFn,
|
setOverlapGraph: emptyFn,
|
||||||
overlapGraph: createGraph(),
|
overlapGraph: createGraph(),
|
||||||
|
setShowFilter: emptyFn,
|
||||||
|
showFilter: false,
|
||||||
|
editTableModeTable: null,
|
||||||
|
setEditTableModeTable: emptyFn,
|
||||||
|
tempFloatingEdge: null,
|
||||||
|
setTempFloatingEdge: emptyFn,
|
||||||
|
startFloatingEdgeCreation: emptyFn,
|
||||||
|
endFloatingEdgeCreation: emptyFn,
|
||||||
|
hoveringTableId: null,
|
||||||
|
setHoveringTableId: emptyFn,
|
||||||
|
showCreateRelationshipNode: emptyFn,
|
||||||
|
hideCreateRelationshipNode: emptyFn,
|
||||||
|
events: new EventEmitter(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,25 +1,73 @@
|
|||||||
import React, { type ReactNode, useCallback, useState } from 'react';
|
import React, {
|
||||||
|
type ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
import type { CanvasContext, CanvasEvent } from './canvas-context';
|
||||||
import { canvasContext } from './canvas-context';
|
import { canvasContext } from './canvas-context';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import {
|
import { adjustTablePositions } from '@/lib/domain/db-table';
|
||||||
adjustTablePositions,
|
|
||||||
shouldShowTablesBySchemaFilter,
|
|
||||||
} from '@/lib/domain/db-table';
|
|
||||||
import { useReactFlow } from '@xyflow/react';
|
import { useReactFlow } from '@xyflow/react';
|
||||||
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
|
import { findOverlappingTables } from '@/pages/editor-page/canvas/canvas-utils';
|
||||||
import type { Graph } from '@/lib/graph';
|
import type { Graph } from '@/lib/graph';
|
||||||
import { createGraph } from '@/lib/graph';
|
import { createGraph } from '@/lib/graph';
|
||||||
|
import { useDiagramFilter } from '../diagram-filter-context/use-diagram-filter';
|
||||||
|
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
import {
|
||||||
|
CREATE_RELATIONSHIP_NODE_ID,
|
||||||
|
type CreateRelationshipNodeType,
|
||||||
|
} from '@/pages/editor-page/canvas/create-relationship-node/create-relationship-node';
|
||||||
|
import { useEventEmitter } from 'ahooks';
|
||||||
|
|
||||||
interface CanvasProviderProps {
|
interface CanvasProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||||
const { tables, relationships, updateTablesState, filteredSchemas } =
|
const {
|
||||||
useChartDB();
|
tables,
|
||||||
const { fitView } = useReactFlow();
|
relationships,
|
||||||
|
updateTablesState,
|
||||||
|
databaseType,
|
||||||
|
areas,
|
||||||
|
diagramId,
|
||||||
|
} = useChartDB();
|
||||||
|
const { filter, loading: filterLoading } = useDiagramFilter();
|
||||||
|
const { fitView, screenToFlowPosition, setNodes } = useReactFlow();
|
||||||
const [overlapGraph, setOverlapGraph] =
|
const [overlapGraph, setOverlapGraph] =
|
||||||
useState<Graph<string>>(createGraph());
|
useState<Graph<string>>(createGraph());
|
||||||
|
const [editTableModeTable, setEditTableModeTable] = useState<{
|
||||||
|
tableId: string;
|
||||||
|
fieldId?: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const events = useEventEmitter<CanvasEvent>();
|
||||||
|
|
||||||
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
|
|
||||||
|
const [tempFloatingEdge, setTempFloatingEdge] =
|
||||||
|
useState<CanvasContext['tempFloatingEdge']>(null);
|
||||||
|
|
||||||
|
const [hoveringTableId, setHoveringTableId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const diagramIdActiveFilterRef = useRef<string>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (filterLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagramIdActiveFilterRef.current === diagramId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
diagramIdActiveFilterRef.current = diagramId;
|
||||||
|
|
||||||
|
setShowFilter(true);
|
||||||
|
}, [filterLoading, diagramId]);
|
||||||
|
|
||||||
const reorderTables = useCallback(
|
const reorderTables = useCallback(
|
||||||
(
|
(
|
||||||
@@ -30,9 +78,19 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
|||||||
const newTables = adjustTablePositions({
|
const newTables = adjustTablePositions({
|
||||||
relationships,
|
relationships,
|
||||||
tables: tables.filter((table) =>
|
tables: tables.filter((table) =>
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
),
|
),
|
||||||
mode: 'all', // Use 'all' mode for manual reordering
|
areas,
|
||||||
|
mode: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedOverlapGraph = findOverlappingTables({
|
const updatedOverlapGraph = findOverlappingTables({
|
||||||
@@ -67,9 +125,77 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
|||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
[filteredSchemas, relationships, tables, updateTablesState, fitView]
|
[
|
||||||
|
filter,
|
||||||
|
relationships,
|
||||||
|
tables,
|
||||||
|
updateTablesState,
|
||||||
|
fitView,
|
||||||
|
databaseType,
|
||||||
|
areas,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const startFloatingEdgeCreation: CanvasContext['startFloatingEdgeCreation'] =
|
||||||
|
useCallback(({ sourceNodeId }) => {
|
||||||
|
setShowFilter(false);
|
||||||
|
setTempFloatingEdge({
|
||||||
|
sourceNodeId,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const endFloatingEdgeCreation: CanvasContext['endFloatingEdgeCreation'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setTempFloatingEdge(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hideCreateRelationshipNode: CanvasContext['hideCreateRelationshipNode'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.filter((n) => n.id !== CREATE_RELATIONSHIP_NODE_ID)
|
||||||
|
);
|
||||||
|
endFloatingEdgeCreation();
|
||||||
|
}, [setNodes, endFloatingEdgeCreation]);
|
||||||
|
|
||||||
|
const showCreateRelationshipNode: CanvasContext['showCreateRelationshipNode'] =
|
||||||
|
useCallback(
|
||||||
|
({ sourceTableId, targetTableId, x, y }) => {
|
||||||
|
setTempFloatingEdge((edge) =>
|
||||||
|
edge
|
||||||
|
? {
|
||||||
|
...edge,
|
||||||
|
targetNodeId: targetTableId,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
const cursorPos = screenToFlowPosition({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newNode: CreateRelationshipNodeType = {
|
||||||
|
id: CREATE_RELATIONSHIP_NODE_ID,
|
||||||
|
type: 'create-relationship',
|
||||||
|
position: cursorPos,
|
||||||
|
data: {
|
||||||
|
sourceTableId,
|
||||||
|
targetTableId,
|
||||||
|
},
|
||||||
|
draggable: true,
|
||||||
|
selectable: false,
|
||||||
|
zIndex: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
setNodes((nds) => {
|
||||||
|
const nodesWithoutOldCreateRelationshipNode = nds.filter(
|
||||||
|
(n) => n.id !== CREATE_RELATIONSHIP_NODE_ID
|
||||||
|
);
|
||||||
|
return [...nodesWithoutOldCreateRelationshipNode, newNode];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[screenToFlowPosition, setNodes]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<canvasContext.Provider
|
<canvasContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -77,6 +203,19 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
|||||||
fitView,
|
fitView,
|
||||||
setOverlapGraph,
|
setOverlapGraph,
|
||||||
overlapGraph,
|
overlapGraph,
|
||||||
|
setShowFilter,
|
||||||
|
showFilter,
|
||||||
|
editTableModeTable,
|
||||||
|
setEditTableModeTable,
|
||||||
|
tempFloatingEdge: tempFloatingEdge,
|
||||||
|
setTempFloatingEdge: setTempFloatingEdge,
|
||||||
|
startFloatingEdgeCreation: startFloatingEdgeCreation,
|
||||||
|
endFloatingEdgeCreation: endFloatingEdgeCreation,
|
||||||
|
hoveringTableId,
|
||||||
|
setHoveringTableId,
|
||||||
|
showCreateRelationshipNode,
|
||||||
|
hideCreateRelationshipNode,
|
||||||
|
events,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { DBDependency } from '@/lib/domain/db-dependency';
|
|||||||
import { EventEmitter } from 'ahooks/lib/useEventEmitter';
|
import { EventEmitter } from 'ahooks/lib/useEventEmitter';
|
||||||
import type { Area } from '@/lib/domain/area';
|
import type { Area } from '@/lib/domain/area';
|
||||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||||
|
import type { Note } from '@/lib/domain/note';
|
||||||
|
|
||||||
export type ChartDBEventType =
|
export type ChartDBEventType =
|
||||||
| 'add_tables'
|
| 'add_tables'
|
||||||
@@ -74,12 +75,13 @@ export interface ChartDBContext {
|
|||||||
dependencies: DBDependency[];
|
dependencies: DBDependency[];
|
||||||
areas: Area[];
|
areas: Area[];
|
||||||
customTypes: DBCustomType[];
|
customTypes: DBCustomType[];
|
||||||
|
notes: Note[];
|
||||||
currentDiagram: Diagram;
|
currentDiagram: Diagram;
|
||||||
events: EventEmitter<ChartDBEvent>;
|
events: EventEmitter<ChartDBEvent>;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
|
||||||
filteredSchemas?: string[];
|
highlightedCustomType?: DBCustomType;
|
||||||
filterSchemas: (schemaIds: string[]) => void;
|
highlightCustomTypeId: (id?: string) => void;
|
||||||
|
|
||||||
// General operations
|
// General operations
|
||||||
updateDiagramId: (id: string) => Promise<void>;
|
updateDiagramId: (id: string) => Promise<void>;
|
||||||
@@ -92,6 +94,10 @@ export interface ChartDBContext {
|
|||||||
updateDiagramUpdatedAt: () => Promise<void>;
|
updateDiagramUpdatedAt: () => Promise<void>;
|
||||||
clearDiagramData: () => Promise<void>;
|
clearDiagramData: () => Promise<void>;
|
||||||
deleteDiagram: () => Promise<void>;
|
deleteDiagram: () => Promise<void>;
|
||||||
|
updateDiagramData: (
|
||||||
|
diagram: Diagram,
|
||||||
|
options?: { forceUpdateStorage?: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
// Database type operations
|
// Database type operations
|
||||||
updateDatabaseType: (databaseType: DatabaseType) => Promise<void>;
|
updateDatabaseType: (databaseType: DatabaseType) => Promise<void>;
|
||||||
@@ -251,6 +257,31 @@ export interface ChartDBContext {
|
|||||||
options?: { updateHistory: boolean }
|
options?: { updateHistory: boolean }
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
|
// Note operations
|
||||||
|
createNote: (attributes?: Partial<Omit<Note, 'id'>>) => Promise<Note>;
|
||||||
|
addNote: (
|
||||||
|
note: Note,
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
addNotes: (
|
||||||
|
notes: Note[],
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
getNote: (id: string) => Note | null;
|
||||||
|
removeNote: (
|
||||||
|
id: string,
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
removeNotes: (
|
||||||
|
ids: string[],
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
updateNote: (
|
||||||
|
id: string,
|
||||||
|
note: Partial<Note>,
|
||||||
|
options?: { updateHistory: boolean }
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
// Custom type operations
|
// Custom type operations
|
||||||
createCustomType: (
|
createCustomType: (
|
||||||
attributes?: Partial<Omit<DBCustomType, 'id'>>
|
attributes?: Partial<Omit<DBCustomType, 'id'>>
|
||||||
@@ -288,9 +319,9 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
dependencies: [],
|
dependencies: [],
|
||||||
areas: [],
|
areas: [],
|
||||||
customTypes: [],
|
customTypes: [],
|
||||||
|
notes: [],
|
||||||
schemas: [],
|
schemas: [],
|
||||||
filteredSchemas: [],
|
highlightCustomTypeId: emptyFn,
|
||||||
filterSchemas: emptyFn,
|
|
||||||
currentDiagram: {
|
currentDiagram: {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -308,6 +339,7 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
loadDiagramFromData: emptyFn,
|
loadDiagramFromData: emptyFn,
|
||||||
clearDiagramData: emptyFn,
|
clearDiagramData: emptyFn,
|
||||||
deleteDiagram: emptyFn,
|
deleteDiagram: emptyFn,
|
||||||
|
updateDiagramData: emptyFn,
|
||||||
|
|
||||||
// Database type operations
|
// Database type operations
|
||||||
updateDatabaseType: emptyFn,
|
updateDatabaseType: emptyFn,
|
||||||
@@ -364,6 +396,15 @@ export const chartDBContext = createContext<ChartDBContext>({
|
|||||||
removeAreas: emptyFn,
|
removeAreas: emptyFn,
|
||||||
updateArea: emptyFn,
|
updateArea: emptyFn,
|
||||||
|
|
||||||
|
// Note operations
|
||||||
|
createNote: emptyFn,
|
||||||
|
addNote: emptyFn,
|
||||||
|
addNotes: emptyFn,
|
||||||
|
getNote: emptyFn,
|
||||||
|
removeNote: emptyFn,
|
||||||
|
removeNotes: emptyFn,
|
||||||
|
updateNote: emptyFn,
|
||||||
|
|
||||||
// Custom type operations
|
// Custom type operations
|
||||||
createCustomType: emptyFn,
|
createCustomType: emptyFn,
|
||||||
addCustomType: emptyFn,
|
addCustomType: emptyFn,
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import { deepCopy, generateId } from '@/lib/utils';
|
import { deepCopy, generateId } from '@/lib/utils';
|
||||||
import { randomColor } from '@/lib/colors';
|
import { defaultTableColor, defaultAreaColor, viewColor } from '@/lib/colors';
|
||||||
import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
|
import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
|
||||||
import { chartDBContext } from './chartdb-context';
|
import { chartDBContext } from './chartdb-context';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import type { DBField } from '@/lib/domain/db-field';
|
import type { DBField } from '@/lib/domain/db-field';
|
||||||
import type { DBIndex } from '@/lib/domain/db-index';
|
import {
|
||||||
|
getTableIndexesWithPrimaryKey,
|
||||||
|
type DBIndex,
|
||||||
|
} from '@/lib/domain/db-index';
|
||||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||||
import { useStorage } from '@/hooks/use-storage';
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||||
@@ -17,11 +20,11 @@ import {
|
|||||||
databasesWithSchemas,
|
databasesWithSchemas,
|
||||||
schemaNameToSchemaId,
|
schemaNameToSchemaId,
|
||||||
} from '@/lib/domain/db-schema';
|
} from '@/lib/domain/db-schema';
|
||||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
|
||||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
import { useEventEmitter } from 'ahooks';
|
import { useEventEmitter } from 'ahooks';
|
||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import type { Area } from '@/lib/domain/area';
|
import type { Area } from '@/lib/domain/area';
|
||||||
|
import type { Note } from '@/lib/domain/note';
|
||||||
import { storageInitialValue } from '../storage-context/storage-context';
|
import { storageInitialValue } from '../storage-context/storage-context';
|
||||||
import { useDiff } from '../diff-context/use-diff';
|
import { useDiff } from '../diff-context/use-diff';
|
||||||
import type { DiffCalculatedEvent } from '../diff-context/diff-context';
|
import type { DiffCalculatedEvent } from '../diff-context/diff-context';
|
||||||
@@ -39,11 +42,11 @@ export const ChartDBProvider: React.FC<
|
|||||||
React.PropsWithChildren<ChartDBProviderProps>
|
React.PropsWithChildren<ChartDBProviderProps>
|
||||||
> = ({ children, diagram, readonly: readonlyProp }) => {
|
> = ({ children, diagram, readonly: readonlyProp }) => {
|
||||||
const { hasDiff } = useDiff();
|
const { hasDiff } = useDiff();
|
||||||
let db = useStorage();
|
const storageDB = useStorage();
|
||||||
const events = useEventEmitter<ChartDBEvent>();
|
const events = useEventEmitter<ChartDBEvent>();
|
||||||
const { setSchemasFilter, schemasFilter } = useLocalConfig();
|
|
||||||
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
const { addUndoAction, resetRedoStack, resetUndoStack } =
|
||||||
useRedoUndoStack();
|
useRedoUndoStack();
|
||||||
|
|
||||||
const [diagramId, setDiagramId] = useState('');
|
const [diagramId, setDiagramId] = useState('');
|
||||||
const [diagramName, setDiagramName] = useState('');
|
const [diagramName, setDiagramName] = useState('');
|
||||||
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
||||||
@@ -65,13 +68,18 @@ export const ChartDBProvider: React.FC<
|
|||||||
const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
|
const [customTypes, setCustomTypes] = useState<DBCustomType[]>(
|
||||||
diagram?.customTypes ?? []
|
diagram?.customTypes ?? []
|
||||||
);
|
);
|
||||||
|
const [notes, setNotes] = useState<Note[]>(diagram?.notes ?? []);
|
||||||
|
|
||||||
const { events: diffEvents } = useDiff();
|
const { events: diffEvents } = useDiff();
|
||||||
|
|
||||||
|
const [highlightedCustomTypeId, setHighlightedCustomTypeId] =
|
||||||
|
useState<string>();
|
||||||
|
|
||||||
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
|
const diffCalculatedHandler = useCallback((event: DiffCalculatedEvent) => {
|
||||||
const { tablesAdded, fieldsAdded, relationshipsAdded } = event.data;
|
const { tablesToAdd, fieldsToAdd, relationshipsToAdd } = event.data;
|
||||||
setTables((tables) =>
|
setTables((tables) =>
|
||||||
[...tables, ...(tablesAdded ?? [])].map((table) => {
|
[...tables, ...(tablesToAdd ?? [])].map((table) => {
|
||||||
const fields = fieldsAdded.get(table.id);
|
const fields = fieldsToAdd.get(table.id);
|
||||||
return fields
|
return fields
|
||||||
? { ...table, fields: [...table.fields, ...fields] }
|
? { ...table, fields: [...table.fields, ...fields] }
|
||||||
: table;
|
: table;
|
||||||
@@ -79,23 +87,22 @@ export const ChartDBProvider: React.FC<
|
|||||||
);
|
);
|
||||||
setRelationships((relationships) => [
|
setRelationships((relationships) => [
|
||||||
...relationships,
|
...relationships,
|
||||||
...(relationshipsAdded ?? []),
|
...(relationshipsToAdd ?? []),
|
||||||
]);
|
]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
diffEvents.useSubscription(diffCalculatedHandler);
|
diffEvents.useSubscription(diffCalculatedHandler);
|
||||||
|
|
||||||
const defaultSchemaName = defaultSchemas[databaseType];
|
const defaultSchemaName = useMemo(
|
||||||
|
() => defaultSchemas[databaseType],
|
||||||
|
[databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
const readonly = useMemo(
|
const readonly = useMemo(
|
||||||
() => readonlyProp ?? hasDiff ?? false,
|
() => readonlyProp ?? hasDiff ?? false,
|
||||||
[readonlyProp, hasDiff]
|
[readonlyProp, hasDiff]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (readonly) {
|
|
||||||
db = storageInitialValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const schemas = useMemo(
|
const schemas = useMemo(
|
||||||
() =>
|
() =>
|
||||||
databasesWithSchemas.includes(databaseType)
|
databasesWithSchemas.includes(databaseType)
|
||||||
@@ -106,9 +113,11 @@ export const ChartDBProvider: React.FC<
|
|||||||
.filter((schema) => !!schema) as string[]
|
.filter((schema) => !!schema) as string[]
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.sort((a, b) =>
|
.sort((a, b) => {
|
||||||
a === defaultSchemaName ? -1 : a.localeCompare(b)
|
if (a === defaultSchemaName) return -1;
|
||||||
)
|
if (b === defaultSchemaName) return 1;
|
||||||
|
return a.localeCompare(b);
|
||||||
|
})
|
||||||
.map(
|
.map(
|
||||||
(schema): DBSchema => ({
|
(schema): DBSchema => ({
|
||||||
id: schemaNameToSchemaId(schema),
|
id: schemaNameToSchemaId(schema),
|
||||||
@@ -122,34 +131,11 @@ export const ChartDBProvider: React.FC<
|
|||||||
[tables, defaultSchemaName, databaseType]
|
[tables, defaultSchemaName, databaseType]
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterSchemas: ChartDBContext['filterSchemas'] = useCallback(
|
const db = useMemo(
|
||||||
(schemaIds) => {
|
() => (readonly ? storageInitialValue : storageDB),
|
||||||
setSchemasFilter((prev) => ({
|
[storageDB, readonly]
|
||||||
...prev,
|
|
||||||
[diagramId]: schemaIds,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[diagramId, setSchemasFilter]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredSchemas: ChartDBContext['filteredSchemas'] = useMemo(() => {
|
|
||||||
if (schemas.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const schemasFilterFromCache =
|
|
||||||
(schemasFilter[diagramId] ?? []).length === 0
|
|
||||||
? undefined // in case of empty filter, skip cache
|
|
||||||
: schemasFilter[diagramId];
|
|
||||||
|
|
||||||
return (
|
|
||||||
schemasFilterFromCache ?? [
|
|
||||||
schemas.find((s) => s.name === defaultSchemaName)?.id ??
|
|
||||||
schemas[0]?.id,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}, [schemasFilter, diagramId, schemas, defaultSchemaName]);
|
|
||||||
|
|
||||||
const currentDiagram: Diagram = useMemo(
|
const currentDiagram: Diagram = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
id: diagramId,
|
id: diagramId,
|
||||||
@@ -163,6 +149,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
dependencies,
|
dependencies,
|
||||||
areas,
|
areas,
|
||||||
customTypes,
|
customTypes,
|
||||||
|
notes,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
diagramId,
|
diagramId,
|
||||||
@@ -174,6 +161,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
dependencies,
|
dependencies,
|
||||||
areas,
|
areas,
|
||||||
customTypes,
|
customTypes,
|
||||||
|
notes,
|
||||||
diagramCreatedAt,
|
diagramCreatedAt,
|
||||||
diagramUpdatedAt,
|
diagramUpdatedAt,
|
||||||
]
|
]
|
||||||
@@ -187,6 +175,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
setDependencies([]);
|
setDependencies([]);
|
||||||
setAreas([]);
|
setAreas([]);
|
||||||
setCustomTypes([]);
|
setCustomTypes([]);
|
||||||
|
setNotes([]);
|
||||||
setDiagramUpdatedAt(updatedAt);
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
resetRedoStack();
|
resetRedoStack();
|
||||||
@@ -199,6 +188,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
db.deleteDiagramDependencies(diagramId),
|
db.deleteDiagramDependencies(diagramId),
|
||||||
db.deleteDiagramAreas(diagramId),
|
db.deleteDiagramAreas(diagramId),
|
||||||
db.deleteDiagramCustomTypes(diagramId),
|
db.deleteDiagramCustomTypes(diagramId),
|
||||||
|
db.deleteDiagramNotes(diagramId),
|
||||||
]);
|
]);
|
||||||
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
||||||
|
|
||||||
@@ -213,6 +203,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
setDependencies([]);
|
setDependencies([]);
|
||||||
setAreas([]);
|
setAreas([]);
|
||||||
setCustomTypes([]);
|
setCustomTypes([]);
|
||||||
|
setNotes([]);
|
||||||
resetRedoStack();
|
resetRedoStack();
|
||||||
resetUndoStack();
|
resetUndoStack();
|
||||||
|
|
||||||
@@ -223,6 +214,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
db.deleteDiagramDependencies(diagramId),
|
db.deleteDiagramDependencies(diagramId),
|
||||||
db.deleteDiagramAreas(diagramId),
|
db.deleteDiagramAreas(diagramId),
|
||||||
db.deleteDiagramCustomTypes(diagramId),
|
db.deleteDiagramCustomTypes(diagramId),
|
||||||
|
db.deleteDiagramNotes(diagramId),
|
||||||
]);
|
]);
|
||||||
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
}, [db, diagramId, resetRedoStack, resetUndoStack]);
|
||||||
|
|
||||||
@@ -361,12 +353,18 @@ export const ChartDBProvider: React.FC<
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
indexes: [],
|
indexes: [],
|
||||||
color: randomColor(),
|
color: attributes?.isView ? viewColor : defaultTableColor,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
isView: false,
|
isView: false,
|
||||||
order: tables.length,
|
order: tables.length,
|
||||||
...attributes,
|
...attributes,
|
||||||
|
schema: attributes?.schema ?? defaultSchemas[databaseType],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
table.indexes = getTableIndexesWithPrimaryKey({
|
||||||
|
table,
|
||||||
|
});
|
||||||
|
|
||||||
await addTable(table);
|
await addTable(table);
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
@@ -658,17 +656,30 @@ export const ChartDBProvider: React.FC<
|
|||||||
options = { updateHistory: true }
|
options = { updateHistory: true }
|
||||||
) => {
|
) => {
|
||||||
const prevField = getField(tableId, fieldId);
|
const prevField = getField(tableId, fieldId);
|
||||||
|
|
||||||
|
const updateTableFn = (table: DBTable) => {
|
||||||
|
const updatedTable: DBTable = {
|
||||||
|
...table,
|
||||||
|
fields: table.fields.map((f) =>
|
||||||
|
f.id === fieldId ? { ...f, ...field } : f
|
||||||
|
),
|
||||||
|
} satisfies DBTable;
|
||||||
|
|
||||||
|
updatedTable.indexes = getTableIndexesWithPrimaryKey({
|
||||||
|
table: updatedTable,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedTable;
|
||||||
|
};
|
||||||
|
|
||||||
setTables((tables) =>
|
setTables((tables) =>
|
||||||
tables.map((table) =>
|
tables.map((table) => {
|
||||||
table.id === tableId
|
if (table.id === tableId) {
|
||||||
? {
|
return updateTableFn(table);
|
||||||
...table,
|
}
|
||||||
fields: table.fields.map((f) =>
|
|
||||||
f.id === fieldId ? { ...f, ...field } : f
|
return table;
|
||||||
),
|
})
|
||||||
}
|
|
||||||
: table
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const table = await db.getTable({ diagramId, id: tableId });
|
const table = await db.getTable({ diagramId, id: tableId });
|
||||||
@@ -683,10 +694,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
db.updateTable({
|
db.updateTable({
|
||||||
id: tableId,
|
id: tableId,
|
||||||
attributes: {
|
attributes: {
|
||||||
...table,
|
...updateTableFn(table),
|
||||||
fields: table.fields.map((f) =>
|
|
||||||
f.id === fieldId ? { ...f, ...field } : f
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -713,19 +721,29 @@ export const ChartDBProvider: React.FC<
|
|||||||
fieldId: string,
|
fieldId: string,
|
||||||
options = { updateHistory: true }
|
options = { updateHistory: true }
|
||||||
) => {
|
) => {
|
||||||
|
const updateTableFn = (table: DBTable) => {
|
||||||
|
const updatedTable: DBTable = {
|
||||||
|
...table,
|
||||||
|
fields: table.fields.filter((f) => f.id !== fieldId),
|
||||||
|
} satisfies DBTable;
|
||||||
|
|
||||||
|
updatedTable.indexes = getTableIndexesWithPrimaryKey({
|
||||||
|
table: updatedTable,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedTable;
|
||||||
|
};
|
||||||
|
|
||||||
const fields = getTable(tableId)?.fields ?? [];
|
const fields = getTable(tableId)?.fields ?? [];
|
||||||
const prevField = getField(tableId, fieldId);
|
const prevField = getField(tableId, fieldId);
|
||||||
setTables((tables) =>
|
setTables((tables) =>
|
||||||
tables.map((table) =>
|
tables.map((table) => {
|
||||||
table.id === tableId
|
if (table.id === tableId) {
|
||||||
? {
|
return updateTableFn(table);
|
||||||
...table,
|
}
|
||||||
fields: table.fields.filter(
|
|
||||||
(f) => f.id !== fieldId
|
return table;
|
||||||
),
|
})
|
||||||
}
|
|
||||||
: table
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
events.emit({
|
events.emit({
|
||||||
@@ -749,8 +767,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
db.updateTable({
|
db.updateTable({
|
||||||
id: tableId,
|
id: tableId,
|
||||||
attributes: {
|
attributes: {
|
||||||
...table,
|
...updateTableFn(table),
|
||||||
fields: table.fields.filter((f) => f.id !== fieldId),
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -1106,12 +1123,15 @@ export const ChartDBProvider: React.FC<
|
|||||||
|
|
||||||
const sourceFieldName = sourceField?.name ?? '';
|
const sourceFieldName = sourceField?.name ?? '';
|
||||||
|
|
||||||
|
const targetTable = getTable(targetTableId);
|
||||||
|
const targetTableSchema = targetTable?.schema;
|
||||||
|
|
||||||
const relationship: DBRelationship = {
|
const relationship: DBRelationship = {
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
name: `${sourceTableName}_${sourceFieldName}_fk`,
|
name: `${sourceTableName}_${sourceFieldName}_fk`,
|
||||||
sourceSchema: sourceTable?.schema,
|
sourceSchema: sourceTable?.schema,
|
||||||
sourceTableId,
|
sourceTableId,
|
||||||
targetSchema: sourceTable?.schema,
|
targetSchema: targetTableSchema,
|
||||||
targetTableId,
|
targetTableId,
|
||||||
sourceFieldId,
|
sourceFieldId,
|
||||||
targetFieldId,
|
targetFieldId,
|
||||||
@@ -1433,7 +1453,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
y: 0,
|
y: 0,
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 200,
|
height: 200,
|
||||||
color: randomColor(),
|
color: defaultAreaColor,
|
||||||
...attributes,
|
...attributes,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1516,22 +1536,162 @@ export const ChartDBProvider: React.FC<
|
|||||||
[db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack]
|
[db, diagramId, setAreas, getArea, addUndoAction, resetRedoStack]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Note operations
|
||||||
|
const addNotes: ChartDBContext['addNotes'] = useCallback(
|
||||||
|
async (notes: Note[], options = { updateHistory: true }) => {
|
||||||
|
setNotes((currentNotes) => [...currentNotes, ...notes]);
|
||||||
|
|
||||||
|
const updatedAt = new Date();
|
||||||
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
...notes.map((note) => db.addNote({ diagramId, note })),
|
||||||
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (options.updateHistory) {
|
||||||
|
addUndoAction({
|
||||||
|
action: 'addNotes',
|
||||||
|
redoData: { notes },
|
||||||
|
undoData: { noteIds: notes.map((n) => n.id) },
|
||||||
|
});
|
||||||
|
resetRedoStack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[db, diagramId, setNotes, addUndoAction, resetRedoStack]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addNote: ChartDBContext['addNote'] = useCallback(
|
||||||
|
async (note: Note, options = { updateHistory: true }) => {
|
||||||
|
return addNotes([note], options);
|
||||||
|
},
|
||||||
|
[addNotes]
|
||||||
|
);
|
||||||
|
|
||||||
|
const createNote: ChartDBContext['createNote'] = useCallback(
|
||||||
|
async (attributes) => {
|
||||||
|
const note: Note = {
|
||||||
|
id: generateId(),
|
||||||
|
content: '',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 200,
|
||||||
|
height: 150,
|
||||||
|
color: '#ffe374', // Default warm yellow
|
||||||
|
...attributes,
|
||||||
|
};
|
||||||
|
|
||||||
|
await addNote(note);
|
||||||
|
|
||||||
|
return note;
|
||||||
|
},
|
||||||
|
[addNote]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getNote: ChartDBContext['getNote'] = useCallback(
|
||||||
|
(id: string) => notes.find((note) => note.id === id) ?? null,
|
||||||
|
[notes]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeNotes: ChartDBContext['removeNotes'] = useCallback(
|
||||||
|
async (ids: string[], options = { updateHistory: true }) => {
|
||||||
|
const prevNotes = [
|
||||||
|
...notes.filter((note) => ids.includes(note.id)),
|
||||||
|
];
|
||||||
|
|
||||||
|
setNotes((notes) => notes.filter((note) => !ids.includes(note.id)));
|
||||||
|
|
||||||
|
const updatedAt = new Date();
|
||||||
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
...ids.map((id) => db.deleteNote({ diagramId, id })),
|
||||||
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (prevNotes.length > 0 && options.updateHistory) {
|
||||||
|
addUndoAction({
|
||||||
|
action: 'removeNotes',
|
||||||
|
redoData: { noteIds: ids },
|
||||||
|
undoData: { notes: prevNotes },
|
||||||
|
});
|
||||||
|
resetRedoStack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[db, diagramId, setNotes, notes, addUndoAction, resetRedoStack]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeNote: ChartDBContext['removeNote'] = useCallback(
|
||||||
|
async (id: string, options = { updateHistory: true }) => {
|
||||||
|
return removeNotes([id], options);
|
||||||
|
},
|
||||||
|
[removeNotes]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateNote: ChartDBContext['updateNote'] = useCallback(
|
||||||
|
async (
|
||||||
|
id: string,
|
||||||
|
note: Partial<Note>,
|
||||||
|
options = { updateHistory: true }
|
||||||
|
) => {
|
||||||
|
const prevNote = getNote(id);
|
||||||
|
|
||||||
|
setNotes((notes) =>
|
||||||
|
notes.map((n) => (n.id === id ? { ...n, ...note } : n))
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedAt = new Date();
|
||||||
|
setDiagramUpdatedAt(updatedAt);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
||||||
|
db.updateNote({ id, attributes: note }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!!prevNote && options.updateHistory) {
|
||||||
|
addUndoAction({
|
||||||
|
action: 'updateNote',
|
||||||
|
redoData: { noteId: id, note },
|
||||||
|
undoData: { noteId: id, note: prevNote },
|
||||||
|
});
|
||||||
|
resetRedoStack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[db, diagramId, setNotes, getNote, addUndoAction, resetRedoStack]
|
||||||
|
);
|
||||||
|
|
||||||
|
const highlightCustomTypeId = useCallback(
|
||||||
|
(id?: string) => setHighlightedCustomTypeId(id),
|
||||||
|
[setHighlightedCustomTypeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const highlightedCustomType = useMemo(() => {
|
||||||
|
return highlightedCustomTypeId
|
||||||
|
? customTypes.find((type) => type.id === highlightedCustomTypeId)
|
||||||
|
: undefined;
|
||||||
|
}, [highlightedCustomTypeId, customTypes]);
|
||||||
|
|
||||||
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
|
const loadDiagramFromData: ChartDBContext['loadDiagramFromData'] =
|
||||||
useCallback(
|
useCallback(
|
||||||
async (diagram) => {
|
(diagram) => {
|
||||||
setDiagramId(diagram.id);
|
setDiagramId(diagram.id);
|
||||||
setDiagramName(diagram.name);
|
setDiagramName(diagram.name);
|
||||||
setDatabaseType(diagram.databaseType);
|
setDatabaseType(diagram.databaseType);
|
||||||
setDatabaseEdition(diagram.databaseEdition);
|
setDatabaseEdition(diagram.databaseEdition);
|
||||||
setTables(diagram?.tables ?? []);
|
setTables(diagram.tables ?? []);
|
||||||
setRelationships(diagram?.relationships ?? []);
|
setRelationships(diagram.relationships ?? []);
|
||||||
setDependencies(diagram?.dependencies ?? []);
|
setDependencies(diagram.dependencies ?? []);
|
||||||
setAreas(diagram?.areas ?? []);
|
setAreas(diagram.areas ?? []);
|
||||||
setCustomTypes(diagram?.customTypes ?? []);
|
setCustomTypes(diagram.customTypes ?? []);
|
||||||
setDiagramCreatedAt(diagram.createdAt);
|
setDiagramCreatedAt(diagram.createdAt);
|
||||||
setDiagramUpdatedAt(diagram.updatedAt);
|
setDiagramUpdatedAt(diagram.updatedAt);
|
||||||
|
setHighlightedCustomTypeId(undefined);
|
||||||
|
setNotes(diagram.notes ?? []);
|
||||||
|
|
||||||
events.emit({ action: 'load_diagram', data: { diagram } });
|
events.emit({ action: 'load_diagram', data: { diagram } });
|
||||||
|
|
||||||
|
resetRedoStack();
|
||||||
|
resetUndoStack();
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
setDiagramId,
|
setDiagramId,
|
||||||
@@ -1545,18 +1705,33 @@ export const ChartDBProvider: React.FC<
|
|||||||
setCustomTypes,
|
setCustomTypes,
|
||||||
setDiagramCreatedAt,
|
setDiagramCreatedAt,
|
||||||
setDiagramUpdatedAt,
|
setDiagramUpdatedAt,
|
||||||
|
setHighlightedCustomTypeId,
|
||||||
events,
|
events,
|
||||||
|
setNotes,
|
||||||
|
resetRedoStack,
|
||||||
|
resetUndoStack,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updateDiagramData: ChartDBContext['updateDiagramData'] = useCallback(
|
||||||
|
async (diagram, options) => {
|
||||||
|
const st = options?.forceUpdateStorage ? storageDB : db;
|
||||||
|
await st.deleteDiagram(diagram.id);
|
||||||
|
await st.addDiagram({ diagram });
|
||||||
|
loadDiagramFromData(diagram);
|
||||||
|
},
|
||||||
|
[db, storageDB, loadDiagramFromData]
|
||||||
|
);
|
||||||
|
|
||||||
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
|
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
|
||||||
async (diagramId: string) => {
|
async (diagramId: string) => {
|
||||||
const diagram = await db.getDiagram(diagramId, {
|
const diagram = await storageDB.getDiagram(diagramId, {
|
||||||
includeRelationships: true,
|
includeRelationships: true,
|
||||||
includeTables: true,
|
includeTables: true,
|
||||||
includeDependencies: true,
|
includeDependencies: true,
|
||||||
includeAreas: true,
|
includeAreas: true,
|
||||||
includeCustomTypes: true,
|
includeCustomTypes: true,
|
||||||
|
includeNotes: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (diagram) {
|
if (diagram) {
|
||||||
@@ -1565,7 +1740,7 @@ export const ChartDBProvider: React.FC<
|
|||||||
|
|
||||||
return diagram;
|
return diagram;
|
||||||
},
|
},
|
||||||
[db, loadDiagramFromData]
|
[storageDB, loadDiagramFromData]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Custom type operations
|
// Custom type operations
|
||||||
@@ -1722,12 +1897,12 @@ export const ChartDBProvider: React.FC<
|
|||||||
relationships,
|
relationships,
|
||||||
dependencies,
|
dependencies,
|
||||||
areas,
|
areas,
|
||||||
|
notes,
|
||||||
currentDiagram,
|
currentDiagram,
|
||||||
schemas,
|
schemas,
|
||||||
filteredSchemas,
|
|
||||||
events,
|
events,
|
||||||
readonly,
|
readonly,
|
||||||
filterSchemas,
|
updateDiagramData,
|
||||||
updateDiagramId,
|
updateDiagramId,
|
||||||
updateDiagramName,
|
updateDiagramName,
|
||||||
loadDiagram,
|
loadDiagram,
|
||||||
@@ -1784,6 +1959,15 @@ export const ChartDBProvider: React.FC<
|
|||||||
removeCustomType,
|
removeCustomType,
|
||||||
removeCustomTypes,
|
removeCustomTypes,
|
||||||
updateCustomType,
|
updateCustomType,
|
||||||
|
highlightCustomTypeId,
|
||||||
|
highlightedCustomType,
|
||||||
|
createNote,
|
||||||
|
addNote,
|
||||||
|
addNotes,
|
||||||
|
getNote,
|
||||||
|
removeNote,
|
||||||
|
removeNotes,
|
||||||
|
updateNote,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ConfigContext } from './config-context';
|
import { ConfigContext } from './config-context';
|
||||||
|
|
||||||
import { useStorage } from '@/hooks/use-storage';
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
@@ -8,7 +8,7 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { getConfig, updateConfig: updateDataConfig } = useStorage();
|
const { getConfig, updateConfig: updateDataConfig } = useStorage();
|
||||||
const [config, setConfig] = React.useState<ChartDBConfig | undefined>();
|
const [config, setConfig] = useState<ChartDBConfig | undefined>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
@@ -45,7 +45,12 @@ export const ConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigContext.Provider value={{ config, updateConfig }}>
|
<ConfigContext.Provider
|
||||||
|
value={{
|
||||||
|
config,
|
||||||
|
updateConfig,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ConfigContext.Provider>
|
</ConfigContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { DBSchema } from '@/lib/domain';
|
||||||
|
import type {
|
||||||
|
DiagramFilter,
|
||||||
|
FilterTableInfo,
|
||||||
|
} from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import { emptyFn } from '@/lib/utils';
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export interface DiagramFilterContext {
|
||||||
|
filter?: DiagramFilter;
|
||||||
|
loading: boolean;
|
||||||
|
|
||||||
|
hasActiveFilter: boolean;
|
||||||
|
schemasDisplayed: DBSchema[];
|
||||||
|
|
||||||
|
clearSchemaIdsFilter: () => void;
|
||||||
|
clearTableIdsFilter: () => void;
|
||||||
|
|
||||||
|
setTableIdsFilterEmpty: () => void;
|
||||||
|
|
||||||
|
// reset
|
||||||
|
resetFilter: () => void;
|
||||||
|
|
||||||
|
toggleSchemaFilter: (schemaId: string) => void;
|
||||||
|
toggleTableFilter: (tableId: string) => void;
|
||||||
|
addSchemaToFilter: (schemaId: string) => void;
|
||||||
|
addTablesToFilter: (attrs: {
|
||||||
|
tableIds?: string[];
|
||||||
|
filterCallback?: (table: FilterTableInfo) => boolean;
|
||||||
|
}) => void;
|
||||||
|
removeTablesFromFilter: (attrs: {
|
||||||
|
tableIds?: string[];
|
||||||
|
filterCallback?: (table: FilterTableInfo) => boolean;
|
||||||
|
}) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const diagramFilterContext = createContext<DiagramFilterContext>({
|
||||||
|
hasActiveFilter: false,
|
||||||
|
clearSchemaIdsFilter: emptyFn,
|
||||||
|
clearTableIdsFilter: emptyFn,
|
||||||
|
setTableIdsFilterEmpty: emptyFn,
|
||||||
|
resetFilter: emptyFn,
|
||||||
|
toggleSchemaFilter: emptyFn,
|
||||||
|
toggleTableFilter: emptyFn,
|
||||||
|
addSchemaToFilter: emptyFn,
|
||||||
|
schemasDisplayed: [],
|
||||||
|
addTablesToFilter: emptyFn,
|
||||||
|
removeTablesFromFilter: emptyFn,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
559
src/context/diagram-filter-context/diagram-filter-provider.tsx
Normal file
559
src/context/diagram-filter-context/diagram-filter-provider.tsx
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import type { DiagramFilterContext } from './diagram-filter-context';
|
||||||
|
import { diagramFilterContext } from './diagram-filter-context';
|
||||||
|
import type {
|
||||||
|
DiagramFilter,
|
||||||
|
FilterTableInfo,
|
||||||
|
} from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import {
|
||||||
|
reduceFilter,
|
||||||
|
spreadFilterTables,
|
||||||
|
} from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { databasesWithSchemas, schemaNameToSchemaId } from '@/lib/domain';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
import type { ChartDBEvent } from '../chartdb-context/chartdb-context';
|
||||||
|
|
||||||
|
export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { diagramId, tables, schemas, databaseType, events } = useChartDB();
|
||||||
|
const { getDiagramFilter, updateDiagramFilter } = useStorage();
|
||||||
|
const [filter, setFilter] = useState<DiagramFilter>({});
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const allSchemasIds = useMemo(() => {
|
||||||
|
return schemas.map((schema) => schema.id);
|
||||||
|
}, [schemas]);
|
||||||
|
|
||||||
|
const allTables: FilterTableInfo[] = useMemo(() => {
|
||||||
|
return tables.map(
|
||||||
|
(table) =>
|
||||||
|
({
|
||||||
|
id: table.id,
|
||||||
|
schemaId: table.schema
|
||||||
|
? schemaNameToSchemaId(table.schema)
|
||||||
|
: defaultSchemas[databaseType],
|
||||||
|
schema: table.schema ?? defaultSchemas[databaseType],
|
||||||
|
areaId: table.parentAreaId ?? undefined,
|
||||||
|
}) satisfies FilterTableInfo
|
||||||
|
);
|
||||||
|
}, [tables, databaseType]);
|
||||||
|
|
||||||
|
const diagramIdOfLoadedFilter = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (diagramId && diagramId === diagramIdOfLoadedFilter.current) {
|
||||||
|
updateDiagramFilter(diagramId, filter);
|
||||||
|
}
|
||||||
|
}, [diagramId, filter, updateDiagramFilter]);
|
||||||
|
|
||||||
|
// Reset filter when diagram changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (diagramIdOfLoadedFilter.current === diagramId) {
|
||||||
|
// If the diagramId hasn't changed, do not reset the filter
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const loadFilterFromStorage = async (diagramId: string) => {
|
||||||
|
if (diagramId) {
|
||||||
|
const storedFilter = await getDiagramFilter(diagramId);
|
||||||
|
|
||||||
|
let filterToSet = storedFilter;
|
||||||
|
|
||||||
|
if (!filterToSet) {
|
||||||
|
// If no filter is stored, set default based on database type
|
||||||
|
filterToSet =
|
||||||
|
schemas.length > 1
|
||||||
|
? { schemaIds: [schemas[0].id] }
|
||||||
|
: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter(filterToSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
setFilter({});
|
||||||
|
|
||||||
|
if (diagramId) {
|
||||||
|
loadFilterFromStorage(diagramId);
|
||||||
|
diagramIdOfLoadedFilter.current = diagramId;
|
||||||
|
}
|
||||||
|
}, [diagramId, getDiagramFilter, schemas]);
|
||||||
|
|
||||||
|
const clearSchemaIds: DiagramFilterContext['clearSchemaIdsFilter'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
schemaIds: undefined,
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clearTableIds: DiagramFilterContext['clearTableIdsFilter'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
tableIds: undefined,
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setTableIdsEmpty: DiagramFilterContext['setTableIdsFilterEmpty'] =
|
||||||
|
useCallback(() => {
|
||||||
|
setFilter(
|
||||||
|
(prev) =>
|
||||||
|
({
|
||||||
|
...prev,
|
||||||
|
tableIds: [],
|
||||||
|
}) satisfies DiagramFilter
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Reset filter
|
||||||
|
const resetFilter: DiagramFilterContext['resetFilter'] = useCallback(() => {
|
||||||
|
setFilter({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleSchemaFilter: DiagramFilterContext['toggleSchemaFilter'] =
|
||||||
|
useCallback(
|
||||||
|
(schemaId: string) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
const currentSchemaIds = prev.schemaIds;
|
||||||
|
|
||||||
|
// Check if schema is currently visible
|
||||||
|
const isSchemaVisible = !allTables.some(
|
||||||
|
(table) =>
|
||||||
|
table.schemaId === schemaId &&
|
||||||
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter: prev,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
}) === false
|
||||||
|
);
|
||||||
|
|
||||||
|
let newSchemaIds: string[] | undefined;
|
||||||
|
let newTableIds: string[] | undefined = prev.tableIds;
|
||||||
|
|
||||||
|
if (isSchemaVisible) {
|
||||||
|
// Schema is visible, make it not visible
|
||||||
|
if (!currentSchemaIds) {
|
||||||
|
// All schemas are visible, create filter with all except this one
|
||||||
|
newSchemaIds = allSchemasIds.filter(
|
||||||
|
(id) => id !== schemaId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Remove this schema from the filter
|
||||||
|
newSchemaIds = currentSchemaIds.filter(
|
||||||
|
(id) => id !== schemaId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove tables from this schema from tableIds if present
|
||||||
|
if (prev.tableIds) {
|
||||||
|
const schemaTableIds = allTables
|
||||||
|
.filter((table) => table.schemaId === schemaId)
|
||||||
|
.map((table) => table.id);
|
||||||
|
newTableIds = prev.tableIds.filter(
|
||||||
|
(id) => !schemaTableIds.includes(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Schema is not visible, make it visible
|
||||||
|
newSchemaIds = [
|
||||||
|
...new Set([...(currentSchemaIds || []), schemaId]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add tables from this schema to tableIds if tableIds is defined
|
||||||
|
if (prev.tableIds) {
|
||||||
|
const schemaTableIds = allTables
|
||||||
|
.filter((table) => table.schemaId === schemaId)
|
||||||
|
.map((table) => table.id);
|
||||||
|
newTableIds = [
|
||||||
|
...new Set([
|
||||||
|
...prev.tableIds,
|
||||||
|
...schemaTableIds,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reduceFilter to optimize and handle edge cases
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
schemaIds: newSchemaIds,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables satisfies FilterTableInfo[],
|
||||||
|
{
|
||||||
|
databaseWithSchemas:
|
||||||
|
databasesWithSchemas.includes(databaseType),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allSchemasIds, allTables, databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleTableFilterForNoSchema = useCallback(
|
||||||
|
(tableId: string) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
const currentTableIds = prev.tableIds;
|
||||||
|
|
||||||
|
// Check if table is currently visible
|
||||||
|
const isTableVisible = filterTable({
|
||||||
|
table: { id: tableId, schema: undefined },
|
||||||
|
filter: prev,
|
||||||
|
options: { defaultSchema: undefined },
|
||||||
|
});
|
||||||
|
|
||||||
|
let newTableIds: string[] | undefined;
|
||||||
|
|
||||||
|
if (isTableVisible) {
|
||||||
|
// Table is visible, make it not visible
|
||||||
|
if (!currentTableIds) {
|
||||||
|
// All tables are visible, create filter with all except this one
|
||||||
|
newTableIds = allTables
|
||||||
|
.filter((t) => t.id !== tableId)
|
||||||
|
.map((t) => t.id);
|
||||||
|
} else {
|
||||||
|
// Remove this table from the filter
|
||||||
|
newTableIds = currentTableIds.filter(
|
||||||
|
(id) => id !== tableId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Table is not visible, make it visible
|
||||||
|
newTableIds = [
|
||||||
|
...new Set([...(currentTableIds || []), tableId]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reduceFilter to optimize and handle edge cases
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
schemaIds: undefined,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables satisfies FilterTableInfo[],
|
||||||
|
{
|
||||||
|
databaseWithSchemas:
|
||||||
|
databasesWithSchemas.includes(databaseType),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allTables, databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleTableFilter: DiagramFilterContext['toggleTableFilter'] =
|
||||||
|
useCallback(
|
||||||
|
(tableId: string) => {
|
||||||
|
if (!databasesWithSchemas.includes(databaseType)) {
|
||||||
|
// No schemas, toggle table filter without schema context
|
||||||
|
toggleTableFilterForNoSchema(tableId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter((prev) => {
|
||||||
|
// Find the table in the tables list
|
||||||
|
const tableInfo = allTables.find((t) => t.id === tableId);
|
||||||
|
|
||||||
|
if (!tableInfo) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if table is currently visible using filterTable
|
||||||
|
const isTableVisible = filterTable({
|
||||||
|
table: {
|
||||||
|
id: tableInfo.id,
|
||||||
|
schema: tableInfo.schema,
|
||||||
|
},
|
||||||
|
filter: prev,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let newSchemaIds = prev.schemaIds;
|
||||||
|
let newTableIds = prev.tableIds;
|
||||||
|
|
||||||
|
if (isTableVisible) {
|
||||||
|
// Table is visible, make it not visible
|
||||||
|
|
||||||
|
// If the table is visible due to its schema being in schemaIds
|
||||||
|
if (
|
||||||
|
tableInfo?.schemaId &&
|
||||||
|
prev.schemaIds?.includes(tableInfo.schemaId)
|
||||||
|
) {
|
||||||
|
// Remove the schema from schemaIds and add all other tables from that schema to tableIds
|
||||||
|
newSchemaIds = prev.schemaIds.filter(
|
||||||
|
(id) => id !== tableInfo.schemaId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get all other tables from this schema (except the one being toggled)
|
||||||
|
const otherTablesFromSchema = allTables
|
||||||
|
.filter(
|
||||||
|
(t) =>
|
||||||
|
t.schemaId === tableInfo.schemaId &&
|
||||||
|
t.id !== tableId
|
||||||
|
)
|
||||||
|
.map((t) => t.id);
|
||||||
|
|
||||||
|
// Add these tables to tableIds
|
||||||
|
newTableIds = [
|
||||||
|
...(prev.tableIds || []),
|
||||||
|
...otherTablesFromSchema,
|
||||||
|
];
|
||||||
|
} else if (prev.tableIds?.includes(tableId)) {
|
||||||
|
// Table is visible because it's in tableIds, remove it
|
||||||
|
newTableIds = prev.tableIds.filter(
|
||||||
|
(id) => id !== tableId
|
||||||
|
);
|
||||||
|
} else if (!prev.tableIds && !prev.schemaIds) {
|
||||||
|
// No filters = all visible, create filter with all tables except this one
|
||||||
|
newTableIds = allTables
|
||||||
|
.filter((t) => t.id !== tableId)
|
||||||
|
.map((t) => t.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Table is not visible, make it visible by adding to tableIds
|
||||||
|
newTableIds = [...(prev.tableIds || []), tableId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reduceFilter to optimize and handle edge cases
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
schemaIds: newSchemaIds,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables satisfies FilterTableInfo[],
|
||||||
|
{
|
||||||
|
databaseWithSchemas:
|
||||||
|
databasesWithSchemas.includes(databaseType),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allTables, databaseType, toggleTableFilterForNoSchema]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addSchemaToFilter: DiagramFilterContext['addSchemaToFilter'] =
|
||||||
|
useCallback(
|
||||||
|
(schemaId: string) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
const currentSchemaIds = prev.schemaIds;
|
||||||
|
if (!currentSchemaIds) {
|
||||||
|
// No schemas are filtered
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If schema is already filtered, do nothing
|
||||||
|
if (currentSchemaIds.includes(schemaId)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add schema to the filter
|
||||||
|
const newSchemaIds = [...currentSchemaIds, schemaId];
|
||||||
|
|
||||||
|
if (newSchemaIds.length === allSchemasIds.length) {
|
||||||
|
// All schemas are now filtered, set to undefined
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
schemaIds: undefined,
|
||||||
|
} satisfies DiagramFilter;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
schemaIds: newSchemaIds,
|
||||||
|
} satisfies DiagramFilter;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allSchemasIds.length]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasActiveFilter: boolean = useMemo(() => {
|
||||||
|
return !!filter.schemaIds || !!filter.tableIds;
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
|
const schemasDisplayed: DiagramFilterContext['schemasDisplayed'] =
|
||||||
|
useMemo(() => {
|
||||||
|
if (!hasActiveFilter) {
|
||||||
|
return schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayedSchemaIds = new Set<string>();
|
||||||
|
for (const table of allTables) {
|
||||||
|
if (
|
||||||
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[databaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
if (table.schemaId) {
|
||||||
|
displayedSchemaIds.add(table.schemaId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return schemas.filter((schema) =>
|
||||||
|
displayedSchemaIds.has(schema.id)
|
||||||
|
);
|
||||||
|
}, [hasActiveFilter, schemas, allTables, filter, databaseType]);
|
||||||
|
|
||||||
|
const addTablesToFilter: DiagramFilterContext['addTablesToFilter'] =
|
||||||
|
useCallback(
|
||||||
|
({ tableIds, filterCallback }) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
let tableIdsToAdd: string[];
|
||||||
|
|
||||||
|
if (tableIds) {
|
||||||
|
// If tableIds are provided, use them directly
|
||||||
|
tableIdsToAdd = tableIds;
|
||||||
|
} else if (filterCallback) {
|
||||||
|
// If filterCallback is provided, filter tables based on it
|
||||||
|
tableIdsToAdd = allTables
|
||||||
|
.filter(filterCallback)
|
||||||
|
.map((table) => table.id);
|
||||||
|
} else {
|
||||||
|
// If neither is provided, do nothing
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterByTableIds = spreadFilterTables(
|
||||||
|
prev,
|
||||||
|
allTables satisfies FilterTableInfo[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentTableIds = filterByTableIds.tableIds || [];
|
||||||
|
const newTableIds = [
|
||||||
|
...new Set([...currentTableIds, ...tableIdsToAdd]),
|
||||||
|
];
|
||||||
|
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
...filterByTableIds,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables satisfies FilterTableInfo[],
|
||||||
|
{
|
||||||
|
databaseWithSchemas:
|
||||||
|
databasesWithSchemas.includes(databaseType),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allTables, databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeTablesFromFilter: DiagramFilterContext['removeTablesFromFilter'] =
|
||||||
|
useCallback(
|
||||||
|
({ tableIds, filterCallback }) => {
|
||||||
|
setFilter((prev) => {
|
||||||
|
let tableIdsToRemovoe: string[];
|
||||||
|
|
||||||
|
if (tableIds) {
|
||||||
|
// If tableIds are provided, use them directly
|
||||||
|
tableIdsToRemovoe = tableIds;
|
||||||
|
} else if (filterCallback) {
|
||||||
|
// If filterCallback is provided, filter tables based on it
|
||||||
|
tableIdsToRemovoe = allTables
|
||||||
|
.filter(filterCallback)
|
||||||
|
.map((table) => table.id);
|
||||||
|
} else {
|
||||||
|
// If neither is provided, do nothing
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterByTableIds = spreadFilterTables(
|
||||||
|
prev,
|
||||||
|
allTables satisfies FilterTableInfo[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentTableIds = filterByTableIds.tableIds || [];
|
||||||
|
const newTableIds = currentTableIds.filter(
|
||||||
|
(id) => !tableIdsToRemovoe.includes(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
return reduceFilter(
|
||||||
|
{
|
||||||
|
...filterByTableIds,
|
||||||
|
tableIds: newTableIds,
|
||||||
|
},
|
||||||
|
allTables satisfies FilterTableInfo[],
|
||||||
|
{
|
||||||
|
databaseWithSchemas:
|
||||||
|
databasesWithSchemas.includes(databaseType),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[allTables, databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const eventConsumer = useCallback(
|
||||||
|
(event: ChartDBEvent) => {
|
||||||
|
if (!hasActiveFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.action === 'add_tables') {
|
||||||
|
addTablesToFilter({
|
||||||
|
tableIds: event.data.tables.map((table) => table.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[hasActiveFilter, addTablesToFilter]
|
||||||
|
);
|
||||||
|
|
||||||
|
events.useSubscription(eventConsumer);
|
||||||
|
|
||||||
|
const value: DiagramFilterContext = {
|
||||||
|
loading,
|
||||||
|
filter,
|
||||||
|
clearSchemaIdsFilter: clearSchemaIds,
|
||||||
|
setTableIdsFilterEmpty: setTableIdsEmpty,
|
||||||
|
clearTableIdsFilter: clearTableIds,
|
||||||
|
resetFilter,
|
||||||
|
toggleSchemaFilter,
|
||||||
|
toggleTableFilter,
|
||||||
|
addSchemaToFilter,
|
||||||
|
hasActiveFilter,
|
||||||
|
schemasDisplayed,
|
||||||
|
addTablesToFilter,
|
||||||
|
removeTablesFromFilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<diagramFilterContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</diagramFilterContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
4
src/context/diagram-filter-context/use-diagram-filter.ts
Normal file
4
src/context/diagram-filter-context/use-diagram-filter.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { diagramFilterContext } from './diagram-filter-context';
|
||||||
|
|
||||||
|
export const useDiagramFilter = () => useContext(diagramFilterContext);
|
||||||
@@ -7,7 +7,6 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
|
|||||||
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
||||||
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-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 { OpenDiagramDialogProps } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
||||||
import type { CreateDiagramDialogProps } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
|
import type { CreateDiagramDialogProps } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
|
||||||
|
|
||||||
@@ -67,12 +66,6 @@ export interface DialogContext {
|
|||||||
params: Omit<ImportDiagramDialogProps, 'dialog'>
|
params: Omit<ImportDiagramDialogProps, 'dialog'>
|
||||||
) => void;
|
) => void;
|
||||||
closeImportDiagramDialog: () => void;
|
closeImportDiagramDialog: () => void;
|
||||||
|
|
||||||
// Import DBML dialog
|
|
||||||
openImportDBMLDialog: (
|
|
||||||
params?: Omit<ImportDBMLDialogProps, 'dialog'>
|
|
||||||
) => void;
|
|
||||||
closeImportDBMLDialog: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dialogContext = createContext<DialogContext>({
|
export const dialogContext = createContext<DialogContext>({
|
||||||
@@ -96,6 +89,4 @@ export const dialogContext = createContext<DialogContext>({
|
|||||||
closeExportDiagramDialog: emptyFn,
|
closeExportDiagramDialog: emptyFn,
|
||||||
openImportDiagramDialog: emptyFn,
|
openImportDiagramDialog: emptyFn,
|
||||||
closeImportDiagramDialog: emptyFn,
|
closeImportDiagramDialog: emptyFn,
|
||||||
openImportDBMLDialog: emptyFn,
|
|
||||||
closeImportDBMLDialog: emptyFn,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor
|
|||||||
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
|
import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog';
|
||||||
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-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> = ({
|
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -132,11 +130,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
|
const [openImportDiagramDialog, setOpenImportDiagramDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
// Import DBML dialog
|
|
||||||
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
|
|
||||||
const [importDBMLDialogParams, setImportDBMLDialogParams] =
|
|
||||||
useState<Omit<ImportDBMLDialogProps, 'dialog'>>();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialogContext.Provider
|
<dialogContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -165,11 +158,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
|
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
|
||||||
closeImportDiagramDialog: () =>
|
closeImportDiagramDialog: () =>
|
||||||
setOpenImportDiagramDialog(false),
|
setOpenImportDiagramDialog(false),
|
||||||
openImportDBMLDialog: (params) => {
|
|
||||||
setImportDBMLDialogParams(params);
|
|
||||||
setOpenImportDBMLDialog(true);
|
|
||||||
},
|
|
||||||
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -204,10 +192,6 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
/>
|
/>
|
||||||
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
|
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
|
||||||
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
|
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
|
||||||
<ImportDBMLDialog
|
|
||||||
dialog={{ open: openImportDBMLDialog }}
|
|
||||||
{...importDBMLDialogParams}
|
|
||||||
/>
|
|
||||||
</dialogContext.Provider>
|
</dialogContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ export type DiffEventBase<T extends DiffEventType, D> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DiffCalculatedData = {
|
export type DiffCalculatedData = {
|
||||||
tablesAdded: DBTable[];
|
tablesToAdd: DBTable[];
|
||||||
fieldsAdded: Map<string, DBField[]>;
|
fieldsToAdd: Map<string, DBField[]>;
|
||||||
relationshipsAdded: DBRelationship[];
|
relationshipsToAdd: DBRelationship[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiffCalculatedEvent = DiffEventBase<
|
export type DiffCalculatedEvent = DiffEventBase<
|
||||||
@@ -32,21 +32,33 @@ export interface DiffContext {
|
|||||||
originalDiagram: Diagram | null;
|
originalDiagram: Diagram | null;
|
||||||
diffMap: DiffMap;
|
diffMap: DiffMap;
|
||||||
hasDiff: boolean;
|
hasDiff: boolean;
|
||||||
|
isSummaryOnly: boolean;
|
||||||
|
|
||||||
calculateDiff: ({
|
calculateDiff: ({
|
||||||
diagram,
|
diagram,
|
||||||
newDiagram,
|
newDiagram,
|
||||||
|
options,
|
||||||
}: {
|
}: {
|
||||||
diagram: Diagram;
|
diagram: Diagram;
|
||||||
newDiagram: Diagram;
|
newDiagram: Diagram;
|
||||||
}) => void;
|
options?: {
|
||||||
|
summaryOnly?: boolean;
|
||||||
|
};
|
||||||
|
}) => { foundDiff: boolean };
|
||||||
|
resetDiff: () => void;
|
||||||
|
|
||||||
// table diff
|
// table diff
|
||||||
checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean;
|
checkIfTableHasChange: ({ tableId }: { tableId: string }) => boolean;
|
||||||
checkIfNewTable: ({ tableId }: { tableId: string }) => boolean;
|
checkIfNewTable: ({ tableId }: { tableId: string }) => boolean;
|
||||||
checkIfTableRemoved: ({ tableId }: { tableId: string }) => boolean;
|
checkIfTableRemoved: ({ tableId }: { tableId: string }) => boolean;
|
||||||
getTableNewName: ({ tableId }: { tableId: string }) => string | null;
|
getTableNewName: ({ tableId }: { tableId: string }) => {
|
||||||
getTableNewColor: ({ tableId }: { tableId: string }) => string | null;
|
old: string;
|
||||||
|
new: string;
|
||||||
|
} | null;
|
||||||
|
getTableNewColor: ({ tableId }: { tableId: string }) => {
|
||||||
|
old: string;
|
||||||
|
new: string;
|
||||||
|
} | null;
|
||||||
|
|
||||||
// field diff
|
// field diff
|
||||||
checkIfFieldHasChange: ({
|
checkIfFieldHasChange: ({
|
||||||
@@ -58,8 +70,46 @@ export interface DiffContext {
|
|||||||
}) => boolean;
|
}) => boolean;
|
||||||
checkIfFieldRemoved: ({ fieldId }: { fieldId: string }) => boolean;
|
checkIfFieldRemoved: ({ fieldId }: { fieldId: string }) => boolean;
|
||||||
checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean;
|
checkIfNewField: ({ fieldId }: { fieldId: string }) => boolean;
|
||||||
getFieldNewName: ({ fieldId }: { fieldId: string }) => string | null;
|
getFieldNewName: ({
|
||||||
getFieldNewType: ({ fieldId }: { fieldId: string }) => DataType | null;
|
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;
|
||||||
|
getFieldNewIsArray: ({
|
||||||
|
fieldId,
|
||||||
|
}: {
|
||||||
|
fieldId: string;
|
||||||
|
}) => { old: boolean; new: boolean } | null;
|
||||||
|
|
||||||
// relationship diff
|
// relationship diff
|
||||||
checkIfNewRelationship: ({
|
checkIfNewRelationship: ({
|
||||||
|
|||||||
@@ -32,10 +32,11 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const [fieldsChanged, setFieldsChanged] = React.useState<
|
const [fieldsChanged, setFieldsChanged] = React.useState<
|
||||||
Map<string, boolean>
|
Map<string, boolean>
|
||||||
>(new Map<string, boolean>());
|
>(new Map<string, boolean>());
|
||||||
|
const [isSummaryOnly, setIsSummaryOnly] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const events = useEventEmitter<DiffEvent>();
|
const events = useEventEmitter<DiffEvent>();
|
||||||
|
|
||||||
const generateNewFieldsMap = useCallback(
|
const generateFieldsToAddMap = useCallback(
|
||||||
({
|
({
|
||||||
diffMap,
|
diffMap,
|
||||||
newDiagram,
|
newDiagram,
|
||||||
@@ -65,7 +66,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const findNewRelationships = useCallback(
|
const findRelationshipsToAdd = useCallback(
|
||||||
({
|
({
|
||||||
diffMap,
|
diffMap,
|
||||||
newDiagram,
|
newDiagram,
|
||||||
@@ -100,7 +101,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
diffMap: DiffMap;
|
diffMap: DiffMap;
|
||||||
}): DiffCalculatedData => {
|
}): DiffCalculatedData => {
|
||||||
return {
|
return {
|
||||||
tablesAdded:
|
tablesToAdd:
|
||||||
newDiagram?.tables?.filter((table) => {
|
newDiagram?.tables?.filter((table) => {
|
||||||
const tableKey = getDiffMapKey({
|
const tableKey = getDiffMapKey({
|
||||||
diffObject: 'table',
|
diffObject: 'table',
|
||||||
@@ -113,21 +114,21 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
);
|
);
|
||||||
}) ?? [],
|
}) ?? [],
|
||||||
|
|
||||||
fieldsAdded: generateNewFieldsMap({
|
fieldsToAdd: generateFieldsToAddMap({
|
||||||
diffMap: diffMap,
|
diffMap: diffMap,
|
||||||
newDiagram: newDiagram,
|
newDiagram: newDiagram,
|
||||||
}),
|
}),
|
||||||
relationshipsAdded: findNewRelationships({
|
relationshipsToAdd: findRelationshipsToAdd({
|
||||||
diffMap: diffMap,
|
diffMap: diffMap,
|
||||||
newDiagram: newDiagram,
|
newDiagram: newDiagram,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[findNewRelationships, generateNewFieldsMap]
|
[findRelationshipsToAdd, generateFieldsToAddMap]
|
||||||
);
|
);
|
||||||
|
|
||||||
const calculateDiff: DiffContext['calculateDiff'] = useCallback(
|
const calculateDiff: DiffContext['calculateDiff'] = useCallback(
|
||||||
({ diagram, newDiagram: newDiagramArg }) => {
|
({ diagram, newDiagram: newDiagramArg, options }) => {
|
||||||
const {
|
const {
|
||||||
diffMap: newDiffs,
|
diffMap: newDiffs,
|
||||||
changedTables: newChangedTables,
|
changedTables: newChangedTables,
|
||||||
@@ -139,6 +140,7 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setFieldsChanged(newChangedFields);
|
setFieldsChanged(newChangedFields);
|
||||||
setNewDiagram(newDiagramArg);
|
setNewDiagram(newDiagramArg);
|
||||||
setOriginalDiagram(diagram);
|
setOriginalDiagram(diagram);
|
||||||
|
setIsSummaryOnly(options?.summaryOnly ?? false);
|
||||||
|
|
||||||
events.emit({
|
events.emit({
|
||||||
action: 'diff_calculated',
|
action: 'diff_calculated',
|
||||||
@@ -147,6 +149,8 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
newDiagram: newDiagramArg,
|
newDiagram: newDiagramArg,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { foundDiff: !!newDiffs.size };
|
||||||
},
|
},
|
||||||
[setDiffMap, events, generateDiffCalculatedData]
|
[setDiffMap, events, generateDiffCalculatedData]
|
||||||
);
|
);
|
||||||
@@ -163,7 +167,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const diff = diffMap.get(tableNameKey);
|
const diff = diffMap.get(tableNameKey);
|
||||||
|
|
||||||
if (diff?.type === 'changed') {
|
if (diff?.type === 'changed') {
|
||||||
return diff.newValue as string;
|
return {
|
||||||
|
new: diff.newValue as string,
|
||||||
|
old: diff.oldValue as string,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +191,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const diff = diffMap.get(tableColorKey);
|
const diff = diffMap.get(tableColorKey);
|
||||||
|
|
||||||
if (diff?.type === 'changed') {
|
if (diff?.type === 'changed') {
|
||||||
return diff.newValue as string;
|
return {
|
||||||
|
new: diff.newValue as string,
|
||||||
|
old: diff.oldValue as string,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -275,7 +285,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const diff = diffMap.get(fieldKey);
|
const diff = diffMap.get(fieldKey);
|
||||||
|
|
||||||
if (diff?.type === 'changed') {
|
if (diff?.type === 'changed') {
|
||||||
return diff.newValue as string;
|
return {
|
||||||
|
old: diff.oldValue as string,
|
||||||
|
new: diff.newValue as string,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +309,160 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const diff = diffMap.get(fieldKey);
|
const diff = diffMap.get(fieldKey);
|
||||||
|
|
||||||
if (diff?.type === 'changed') {
|
if (diff?.type === 'changed') {
|
||||||
return diff.newValue as DataType;
|
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 null;
|
||||||
|
},
|
||||||
|
[diffMap]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getFieldNewIsArray = useCallback<DiffContext['getFieldNewIsArray']>(
|
||||||
|
({ fieldId }) => {
|
||||||
|
const fieldKey = getDiffMapKey({
|
||||||
|
diffObject: 'field',
|
||||||
|
objectId: fieldId,
|
||||||
|
attribute: 'isArray',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (diffMap.has(fieldKey)) {
|
||||||
|
const diff = diffMap.get(fieldKey);
|
||||||
|
|
||||||
|
if (diff?.type === 'changed') {
|
||||||
|
return {
|
||||||
|
old: diff.oldValue as boolean,
|
||||||
|
new: diff.newValue as boolean,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +505,15 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[diffMap]
|
[diffMap]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const resetDiff = useCallback<DiffContext['resetDiff']>(() => {
|
||||||
|
setDiffMap(new Map<string, ChartDBDiff>());
|
||||||
|
setTablesChanged(new Map<string, boolean>());
|
||||||
|
setFieldsChanged(new Map<string, boolean>());
|
||||||
|
setNewDiagram(null);
|
||||||
|
setOriginalDiagram(null);
|
||||||
|
setIsSummaryOnly(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<diffContext.Provider
|
<diffContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -346,8 +521,10 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
originalDiagram,
|
originalDiagram,
|
||||||
diffMap,
|
diffMap,
|
||||||
hasDiff: diffMap.size > 0,
|
hasDiff: diffMap.size > 0,
|
||||||
|
isSummaryOnly,
|
||||||
|
|
||||||
calculateDiff,
|
calculateDiff,
|
||||||
|
resetDiff,
|
||||||
|
|
||||||
// table diff
|
// table diff
|
||||||
getTableNewName,
|
getTableNewName,
|
||||||
@@ -362,6 +539,12 @@ export const DiffProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
checkIfNewField,
|
checkIfNewField,
|
||||||
getFieldNewName,
|
getFieldNewName,
|
||||||
getFieldNewType,
|
getFieldNewType,
|
||||||
|
getFieldNewPrimaryKey,
|
||||||
|
getFieldNewNullable,
|
||||||
|
getFieldNewCharacterMaximumLength,
|
||||||
|
getFieldNewScale,
|
||||||
|
getFieldNewPrecision,
|
||||||
|
getFieldNewIsArray,
|
||||||
|
|
||||||
// relationship diff
|
// relationship diff
|
||||||
checkIfNewRelationship,
|
checkIfNewRelationship,
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
addCustomTypes,
|
addCustomTypes,
|
||||||
removeCustomTypes,
|
removeCustomTypes,
|
||||||
updateCustomType,
|
updateCustomType,
|
||||||
|
addNotes,
|
||||||
|
removeNotes,
|
||||||
|
updateNote,
|
||||||
} = useChartDB();
|
} = useChartDB();
|
||||||
|
|
||||||
const redoActionHandlers = useMemo(
|
const redoActionHandlers = useMemo(
|
||||||
@@ -135,6 +138,15 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
updateHistory: false,
|
updateHistory: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addNotes: ({ redoData: { notes } }) => {
|
||||||
|
return addNotes(notes, { updateHistory: false });
|
||||||
|
},
|
||||||
|
removeNotes: ({ redoData: { noteIds } }) => {
|
||||||
|
return removeNotes(noteIds, { updateHistory: false });
|
||||||
|
},
|
||||||
|
updateNote: ({ redoData: { noteId, note } }) => {
|
||||||
|
return updateNote(noteId, note, { updateHistory: false });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
addTables,
|
addTables,
|
||||||
@@ -160,6 +172,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
addCustomTypes,
|
addCustomTypes,
|
||||||
removeCustomTypes,
|
removeCustomTypes,
|
||||||
updateCustomType,
|
updateCustomType,
|
||||||
|
addNotes,
|
||||||
|
removeNotes,
|
||||||
|
updateNote,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -271,6 +286,15 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
updateHistory: false,
|
updateHistory: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addNotes: ({ undoData: { noteIds } }) => {
|
||||||
|
return removeNotes(noteIds, { updateHistory: false });
|
||||||
|
},
|
||||||
|
removeNotes: ({ undoData: { notes } }) => {
|
||||||
|
return addNotes(notes, { updateHistory: false });
|
||||||
|
},
|
||||||
|
updateNote: ({ undoData: { noteId, note } }) => {
|
||||||
|
return updateNote(noteId, note, { updateHistory: false });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
addTables,
|
addTables,
|
||||||
@@ -296,6 +320,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
addCustomTypes,
|
addCustomTypes,
|
||||||
removeCustomTypes,
|
removeCustomTypes,
|
||||||
updateCustomType,
|
updateCustomType,
|
||||||
|
addNotes,
|
||||||
|
removeNotes,
|
||||||
|
updateNote,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { DBRelationship } from '@/lib/domain/db-relationship';
|
|||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import type { Area } from '@/lib/domain/area';
|
import type { Area } from '@/lib/domain/area';
|
||||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||||
|
import type { Note } from '@/lib/domain/note';
|
||||||
|
|
||||||
type Action = keyof ChartDBContext;
|
type Action = keyof ChartDBContext;
|
||||||
|
|
||||||
@@ -161,6 +162,24 @@ type RedoUndoActionRemoveCustomTypes = RedoUndoActionBase<
|
|||||||
{ customTypes: DBCustomType[] }
|
{ customTypes: DBCustomType[] }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type RedoUndoActionAddNotes = RedoUndoActionBase<
|
||||||
|
'addNotes',
|
||||||
|
{ notes: Note[] },
|
||||||
|
{ noteIds: string[] }
|
||||||
|
>;
|
||||||
|
|
||||||
|
type RedoUndoActionUpdateNote = RedoUndoActionBase<
|
||||||
|
'updateNote',
|
||||||
|
{ noteId: string; note: Partial<Note> },
|
||||||
|
{ noteId: string; note: Partial<Note> }
|
||||||
|
>;
|
||||||
|
|
||||||
|
type RedoUndoActionRemoveNotes = RedoUndoActionBase<
|
||||||
|
'removeNotes',
|
||||||
|
{ noteIds: string[] },
|
||||||
|
{ notes: Note[] }
|
||||||
|
>;
|
||||||
|
|
||||||
export type RedoUndoAction =
|
export type RedoUndoAction =
|
||||||
| RedoUndoActionAddTables
|
| RedoUndoActionAddTables
|
||||||
| RedoUndoActionRemoveTables
|
| RedoUndoActionRemoveTables
|
||||||
@@ -184,7 +203,10 @@ export type RedoUndoAction =
|
|||||||
| RedoUndoActionRemoveAreas
|
| RedoUndoActionRemoveAreas
|
||||||
| RedoUndoActionAddCustomTypes
|
| RedoUndoActionAddCustomTypes
|
||||||
| RedoUndoActionUpdateCustomType
|
| RedoUndoActionUpdateCustomType
|
||||||
| RedoUndoActionRemoveCustomTypes;
|
| RedoUndoActionRemoveCustomTypes
|
||||||
|
| RedoUndoActionAddNotes
|
||||||
|
| RedoUndoActionUpdateNote
|
||||||
|
| RedoUndoActionRemoveNotes;
|
||||||
|
|
||||||
export type RedoActionData<T extends Action> = Extract<
|
export type RedoActionData<T extends Action> = Extract<
|
||||||
RedoUndoAction,
|
RedoUndoAction,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export enum KeyboardShortcutAction {
|
|||||||
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
|
TOGGLE_SIDE_PANEL = 'toggle_side_panel',
|
||||||
SHOW_ALL = 'show_all',
|
SHOW_ALL = 'show_all',
|
||||||
TOGGLE_THEME = 'toggle_theme',
|
TOGGLE_THEME = 'toggle_theme',
|
||||||
|
TOGGLE_FILTER = 'toggle_filter',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyboardShortcut {
|
export interface KeyboardShortcut {
|
||||||
@@ -71,6 +72,13 @@ export const keyboardShortcuts: Record<
|
|||||||
keyCombinationMac: 'meta+m',
|
keyCombinationMac: 'meta+m',
|
||||||
keyCombinationWin: 'ctrl+m',
|
keyCombinationWin: 'ctrl+m',
|
||||||
},
|
},
|
||||||
|
[KeyboardShortcutAction.TOGGLE_FILTER]: {
|
||||||
|
action: KeyboardShortcutAction.TOGGLE_FILTER,
|
||||||
|
keyCombinationLabelMac: '⌘F',
|
||||||
|
keyCombinationLabelWin: 'Ctrl+F',
|
||||||
|
keyCombinationMac: 'meta+f',
|
||||||
|
keyCombinationWin: 'ctrl+f',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface KeyboardShortcutForOS {
|
export interface KeyboardShortcutForOS {
|
||||||
|
|||||||
@@ -2,29 +2,37 @@ import { emptyFn } from '@/lib/utils';
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
export type SidebarSection =
|
export type SidebarSection =
|
||||||
|
| 'dbml'
|
||||||
| 'tables'
|
| 'tables'
|
||||||
| 'relationships'
|
| 'refs'
|
||||||
| 'dependencies'
|
| 'customTypes'
|
||||||
| 'areas'
|
| 'visuals';
|
||||||
| 'customTypes';
|
|
||||||
|
export type VisualsTab = 'areas' | 'notes';
|
||||||
|
|
||||||
export interface LayoutContext {
|
export interface LayoutContext {
|
||||||
openedTableInSidebar: string | undefined;
|
openedTableInSidebar: string | undefined;
|
||||||
openTableFromSidebar: (tableId: string) => void;
|
openTableFromSidebar: (tableId: string) => void;
|
||||||
closeAllTablesInSidebar: () => void;
|
closeAllTablesInSidebar: () => void;
|
||||||
|
|
||||||
openedRelationshipInSidebar: string | undefined;
|
|
||||||
openRelationshipFromSidebar: (relationshipId: string) => void;
|
openRelationshipFromSidebar: (relationshipId: string) => void;
|
||||||
closeAllRelationshipsInSidebar: () => void;
|
closeAllRelationshipsInSidebar: () => void;
|
||||||
|
|
||||||
openedDependencyInSidebar: string | undefined;
|
|
||||||
openDependencyFromSidebar: (dependencyId: string) => void;
|
openDependencyFromSidebar: (dependencyId: string) => void;
|
||||||
closeAllDependenciesInSidebar: () => void;
|
closeAllDependenciesInSidebar: () => void;
|
||||||
|
|
||||||
|
openedRefInSidebar: string | undefined;
|
||||||
|
openRefFromSidebar: (refId: string) => void;
|
||||||
|
closeAllRefsInSidebar: () => void;
|
||||||
|
|
||||||
openedAreaInSidebar: string | undefined;
|
openedAreaInSidebar: string | undefined;
|
||||||
openAreaFromSidebar: (areaId: string) => void;
|
openAreaFromSidebar: (areaId: string) => void;
|
||||||
closeAllAreasInSidebar: () => void;
|
closeAllAreasInSidebar: () => void;
|
||||||
|
|
||||||
|
openedNoteInSidebar: string | undefined;
|
||||||
|
openNoteFromSidebar: (noteId: string) => void;
|
||||||
|
closeAllNotesInSidebar: () => void;
|
||||||
|
|
||||||
openedCustomTypeInSidebar: string | undefined;
|
openedCustomTypeInSidebar: string | undefined;
|
||||||
openCustomTypeFromSidebar: (customTypeId: string) => void;
|
openCustomTypeFromSidebar: (customTypeId: string) => void;
|
||||||
closeAllCustomTypesInSidebar: () => void;
|
closeAllCustomTypesInSidebar: () => void;
|
||||||
@@ -32,32 +40,37 @@ export interface LayoutContext {
|
|||||||
selectedSidebarSection: SidebarSection;
|
selectedSidebarSection: SidebarSection;
|
||||||
selectSidebarSection: (section: SidebarSection) => void;
|
selectSidebarSection: (section: SidebarSection) => void;
|
||||||
|
|
||||||
|
selectedVisualsTab: VisualsTab;
|
||||||
|
selectVisualsTab: (tab: VisualsTab) => void;
|
||||||
|
|
||||||
isSidePanelShowed: boolean;
|
isSidePanelShowed: boolean;
|
||||||
hideSidePanel: () => void;
|
hideSidePanel: () => void;
|
||||||
showSidePanel: () => void;
|
showSidePanel: () => void;
|
||||||
toggleSidePanel: () => void;
|
toggleSidePanel: () => void;
|
||||||
|
|
||||||
isSelectSchemaOpen: boolean;
|
|
||||||
openSelectSchema: () => void;
|
|
||||||
closeSelectSchema: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const layoutContext = createContext<LayoutContext>({
|
export const layoutContext = createContext<LayoutContext>({
|
||||||
openedTableInSidebar: undefined,
|
openedTableInSidebar: undefined,
|
||||||
selectedSidebarSection: 'tables',
|
selectedSidebarSection: 'tables',
|
||||||
|
|
||||||
openedRelationshipInSidebar: undefined,
|
|
||||||
openRelationshipFromSidebar: emptyFn,
|
openRelationshipFromSidebar: emptyFn,
|
||||||
closeAllRelationshipsInSidebar: emptyFn,
|
closeAllRelationshipsInSidebar: emptyFn,
|
||||||
|
|
||||||
openedDependencyInSidebar: undefined,
|
|
||||||
openDependencyFromSidebar: emptyFn,
|
openDependencyFromSidebar: emptyFn,
|
||||||
closeAllDependenciesInSidebar: emptyFn,
|
closeAllDependenciesInSidebar: emptyFn,
|
||||||
|
|
||||||
|
openedRefInSidebar: undefined,
|
||||||
|
openRefFromSidebar: emptyFn,
|
||||||
|
closeAllRefsInSidebar: emptyFn,
|
||||||
|
|
||||||
openedAreaInSidebar: undefined,
|
openedAreaInSidebar: undefined,
|
||||||
openAreaFromSidebar: emptyFn,
|
openAreaFromSidebar: emptyFn,
|
||||||
closeAllAreasInSidebar: emptyFn,
|
closeAllAreasInSidebar: emptyFn,
|
||||||
|
|
||||||
|
openedNoteInSidebar: undefined,
|
||||||
|
openNoteFromSidebar: emptyFn,
|
||||||
|
closeAllNotesInSidebar: emptyFn,
|
||||||
|
|
||||||
openedCustomTypeInSidebar: undefined,
|
openedCustomTypeInSidebar: undefined,
|
||||||
openCustomTypeFromSidebar: emptyFn,
|
openCustomTypeFromSidebar: emptyFn,
|
||||||
closeAllCustomTypesInSidebar: emptyFn,
|
closeAllCustomTypesInSidebar: emptyFn,
|
||||||
@@ -66,12 +79,11 @@ export const layoutContext = createContext<LayoutContext>({
|
|||||||
openTableFromSidebar: emptyFn,
|
openTableFromSidebar: emptyFn,
|
||||||
closeAllTablesInSidebar: emptyFn,
|
closeAllTablesInSidebar: emptyFn,
|
||||||
|
|
||||||
|
selectedVisualsTab: 'areas',
|
||||||
|
selectVisualsTab: emptyFn,
|
||||||
|
|
||||||
isSidePanelShowed: false,
|
isSidePanelShowed: false,
|
||||||
hideSidePanel: emptyFn,
|
hideSidePanel: emptyFn,
|
||||||
showSidePanel: emptyFn,
|
showSidePanel: emptyFn,
|
||||||
toggleSidePanel: emptyFn,
|
toggleSidePanel: emptyFn,
|
||||||
|
|
||||||
isSelectSchemaOpen: false,
|
|
||||||
openSelectSchema: emptyFn,
|
|
||||||
closeSelectSchema: emptyFn,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { LayoutContext, SidebarSection } from './layout-context';
|
import type {
|
||||||
|
LayoutContext,
|
||||||
|
SidebarSection,
|
||||||
|
VisualsTab,
|
||||||
|
} from './layout-context';
|
||||||
import { layoutContext } from './layout-context';
|
import { layoutContext } from './layout-context';
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
|
|
||||||
@@ -10,34 +14,42 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState<
|
const [openedTableInSidebar, setOpenedTableInSidebar] = React.useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
const [openedRelationshipInSidebar, setOpenedRelationshipInSidebar] =
|
const [openedRefInSidebar, setOpenedRefInSidebar] = React.useState<
|
||||||
React.useState<string | undefined>();
|
string | undefined
|
||||||
const [openedDependencyInSidebar, setOpenedDependencyInSidebar] =
|
>();
|
||||||
React.useState<string | undefined>();
|
|
||||||
const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState<
|
const [openedAreaInSidebar, setOpenedAreaInSidebar] = React.useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
|
const [openedNoteInSidebar, setOpenedNoteInSidebar] = React.useState<
|
||||||
|
string | undefined
|
||||||
|
>();
|
||||||
const [openedCustomTypeInSidebar, setOpenedCustomTypeInSidebar] =
|
const [openedCustomTypeInSidebar, setOpenedCustomTypeInSidebar] =
|
||||||
React.useState<string | undefined>();
|
React.useState<string | undefined>();
|
||||||
const [selectedSidebarSection, setSelectedSidebarSection] =
|
const [selectedSidebarSection, setSelectedSidebarSection] =
|
||||||
React.useState<SidebarSection>('tables');
|
React.useState<SidebarSection>('tables');
|
||||||
|
const [selectedVisualsTab, setSelectedVisualsTab] =
|
||||||
|
React.useState<VisualsTab>('areas');
|
||||||
const [isSidePanelShowed, setIsSidePanelShowed] =
|
const [isSidePanelShowed, setIsSidePanelShowed] =
|
||||||
React.useState<boolean>(isDesktop);
|
React.useState<boolean>(isDesktop);
|
||||||
const [isSelectSchemaOpen, setIsSelectSchemaOpen] =
|
|
||||||
React.useState<boolean>(false);
|
|
||||||
|
|
||||||
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
||||||
() => setOpenedTableInSidebar('');
|
() => setOpenedTableInSidebar('');
|
||||||
|
|
||||||
const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] =
|
const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] =
|
||||||
() => setOpenedRelationshipInSidebar('');
|
() => setOpenedRefInSidebar('');
|
||||||
|
|
||||||
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
|
const closeAllDependenciesInSidebar: LayoutContext['closeAllDependenciesInSidebar'] =
|
||||||
() => setOpenedDependencyInSidebar('');
|
() => setOpenedRefInSidebar('');
|
||||||
|
|
||||||
|
const closeAllRefsInSidebar: LayoutContext['closeAllRefsInSidebar'] = () =>
|
||||||
|
setOpenedRefInSidebar('');
|
||||||
|
|
||||||
const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] =
|
const closeAllAreasInSidebar: LayoutContext['closeAllAreasInSidebar'] =
|
||||||
() => setOpenedAreaInSidebar('');
|
() => setOpenedAreaInSidebar('');
|
||||||
|
|
||||||
|
const closeAllNotesInSidebar: LayoutContext['closeAllNotesInSidebar'] =
|
||||||
|
() => setOpenedNoteInSidebar('');
|
||||||
|
|
||||||
const closeAllCustomTypesInSidebar: LayoutContext['closeAllCustomTypesInSidebar'] =
|
const closeAllCustomTypesInSidebar: LayoutContext['closeAllCustomTypesInSidebar'] =
|
||||||
() => setOpenedCustomTypeInSidebar('');
|
() => setOpenedCustomTypeInSidebar('');
|
||||||
|
|
||||||
@@ -62,25 +74,41 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] =
|
const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] =
|
||||||
(relationshipId) => {
|
(relationshipId) => {
|
||||||
showSidePanel();
|
showSidePanel();
|
||||||
setSelectedSidebarSection('relationships');
|
setSelectedSidebarSection('refs');
|
||||||
setOpenedRelationshipInSidebar(relationshipId);
|
setOpenedRefInSidebar(relationshipId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] =
|
const openDependencyFromSidebar: LayoutContext['openDependencyFromSidebar'] =
|
||||||
(dependencyId) => {
|
(dependencyId) => {
|
||||||
showSidePanel();
|
showSidePanel();
|
||||||
setSelectedSidebarSection('dependencies');
|
setSelectedSidebarSection('refs');
|
||||||
setOpenedDependencyInSidebar(dependencyId);
|
setOpenedRefInSidebar(dependencyId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openRefFromSidebar: LayoutContext['openRefFromSidebar'] = (refId) => {
|
||||||
|
showSidePanel();
|
||||||
|
setSelectedSidebarSection('refs');
|
||||||
|
setOpenedRefInSidebar(refId);
|
||||||
|
};
|
||||||
|
|
||||||
const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = (
|
const openAreaFromSidebar: LayoutContext['openAreaFromSidebar'] = (
|
||||||
areaId
|
areaId
|
||||||
) => {
|
) => {
|
||||||
showSidePanel();
|
showSidePanel();
|
||||||
setSelectedSidebarSection('areas');
|
setSelectedSidebarSection('visuals');
|
||||||
|
setSelectedVisualsTab('areas');
|
||||||
setOpenedAreaInSidebar(areaId);
|
setOpenedAreaInSidebar(areaId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openNoteFromSidebar: LayoutContext['openNoteFromSidebar'] = (
|
||||||
|
noteId
|
||||||
|
) => {
|
||||||
|
showSidePanel();
|
||||||
|
setSelectedSidebarSection('visuals');
|
||||||
|
setSelectedVisualsTab('notes');
|
||||||
|
setOpenedNoteInSidebar(noteId);
|
||||||
|
};
|
||||||
|
|
||||||
const openCustomTypeFromSidebar: LayoutContext['openCustomTypeFromSidebar'] =
|
const openCustomTypeFromSidebar: LayoutContext['openCustomTypeFromSidebar'] =
|
||||||
(customTypeId) => {
|
(customTypeId) => {
|
||||||
showSidePanel();
|
showSidePanel();
|
||||||
@@ -88,11 +116,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setOpenedTableInSidebar(customTypeId);
|
setOpenedTableInSidebar(customTypeId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openSelectSchema: LayoutContext['openSelectSchema'] = () =>
|
|
||||||
setIsSelectSchemaOpen(true);
|
|
||||||
|
|
||||||
const closeSelectSchema: LayoutContext['closeSelectSchema'] = () =>
|
|
||||||
setIsSelectSchemaOpen(false);
|
|
||||||
return (
|
return (
|
||||||
<layoutContext.Provider
|
<layoutContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -100,7 +123,6 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
selectedSidebarSection,
|
selectedSidebarSection,
|
||||||
openTableFromSidebar,
|
openTableFromSidebar,
|
||||||
selectSidebarSection: setSelectedSidebarSection,
|
selectSidebarSection: setSelectedSidebarSection,
|
||||||
openedRelationshipInSidebar,
|
|
||||||
openRelationshipFromSidebar,
|
openRelationshipFromSidebar,
|
||||||
closeAllTablesInSidebar,
|
closeAllTablesInSidebar,
|
||||||
closeAllRelationshipsInSidebar,
|
closeAllRelationshipsInSidebar,
|
||||||
@@ -108,18 +130,22 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
hideSidePanel,
|
hideSidePanel,
|
||||||
showSidePanel,
|
showSidePanel,
|
||||||
toggleSidePanel,
|
toggleSidePanel,
|
||||||
isSelectSchemaOpen,
|
|
||||||
openSelectSchema,
|
|
||||||
closeSelectSchema,
|
|
||||||
openedDependencyInSidebar,
|
|
||||||
openDependencyFromSidebar,
|
openDependencyFromSidebar,
|
||||||
closeAllDependenciesInSidebar,
|
closeAllDependenciesInSidebar,
|
||||||
|
openedRefInSidebar,
|
||||||
|
openRefFromSidebar,
|
||||||
|
closeAllRefsInSidebar,
|
||||||
openedAreaInSidebar,
|
openedAreaInSidebar,
|
||||||
openAreaFromSidebar,
|
openAreaFromSidebar,
|
||||||
closeAllAreasInSidebar,
|
closeAllAreasInSidebar,
|
||||||
|
openedNoteInSidebar,
|
||||||
|
openNoteFromSidebar,
|
||||||
|
closeAllNotesInSidebar,
|
||||||
openedCustomTypeInSidebar,
|
openedCustomTypeInSidebar,
|
||||||
openCustomTypeFromSidebar,
|
openCustomTypeFromSidebar,
|
||||||
closeAllCustomTypesInSidebar,
|
closeAllCustomTypesInSidebar,
|
||||||
|
selectedVisualsTab,
|
||||||
|
selectVisualsTab: setSelectedVisualsTab,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import type { Theme } from '../theme-context/theme-context';
|
|||||||
|
|
||||||
export type ScrollAction = 'pan' | 'zoom';
|
export type ScrollAction = 'pan' | 'zoom';
|
||||||
|
|
||||||
export type SchemasFilter = Record<string, string[]>;
|
|
||||||
|
|
||||||
export interface LocalConfigContext {
|
export interface LocalConfigContext {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
setTheme: (theme: Theme) => void;
|
setTheme: (theme: Theme) => void;
|
||||||
@@ -13,16 +11,14 @@ export interface LocalConfigContext {
|
|||||||
scrollAction: ScrollAction;
|
scrollAction: ScrollAction;
|
||||||
setScrollAction: (action: ScrollAction) => void;
|
setScrollAction: (action: ScrollAction) => void;
|
||||||
|
|
||||||
schemasFilter: SchemasFilter;
|
showDBViews: boolean;
|
||||||
setSchemasFilter: React.Dispatch<React.SetStateAction<SchemasFilter>>;
|
setShowDBViews: (showViews: boolean) => void;
|
||||||
|
|
||||||
showCardinality: boolean;
|
showCardinality: boolean;
|
||||||
setShowCardinality: (showCardinality: boolean) => void;
|
setShowCardinality: (showCardinality: boolean) => void;
|
||||||
|
|
||||||
hideMultiSchemaNotification: boolean;
|
showFieldAttributes: boolean;
|
||||||
setHideMultiSchemaNotification: (
|
setShowFieldAttributes: (showFieldAttributes: boolean) => void;
|
||||||
hideMultiSchemaNotification: boolean
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
githubRepoOpened: boolean;
|
githubRepoOpened: boolean;
|
||||||
setGithubRepoOpened: (githubRepoOpened: boolean) => void;
|
setGithubRepoOpened: (githubRepoOpened: boolean) => void;
|
||||||
@@ -30,9 +26,6 @@ export interface LocalConfigContext {
|
|||||||
starUsDialogLastOpen: number;
|
starUsDialogLastOpen: number;
|
||||||
setStarUsDialogLastOpen: (lastOpen: number) => void;
|
setStarUsDialogLastOpen: (lastOpen: number) => void;
|
||||||
|
|
||||||
showDependenciesOnCanvas: boolean;
|
|
||||||
setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void;
|
|
||||||
|
|
||||||
showMiniMapOnCanvas: boolean;
|
showMiniMapOnCanvas: boolean;
|
||||||
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
|
setShowMiniMapOnCanvas: (showMiniMapOnCanvas: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -44,14 +37,14 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
|||||||
scrollAction: 'pan',
|
scrollAction: 'pan',
|
||||||
setScrollAction: emptyFn,
|
setScrollAction: emptyFn,
|
||||||
|
|
||||||
schemasFilter: {},
|
showDBViews: false,
|
||||||
setSchemasFilter: emptyFn,
|
setShowDBViews: emptyFn,
|
||||||
|
|
||||||
showCardinality: true,
|
showCardinality: true,
|
||||||
setShowCardinality: emptyFn,
|
setShowCardinality: emptyFn,
|
||||||
|
|
||||||
hideMultiSchemaNotification: false,
|
showFieldAttributes: true,
|
||||||
setHideMultiSchemaNotification: emptyFn,
|
setShowFieldAttributes: emptyFn,
|
||||||
|
|
||||||
githubRepoOpened: false,
|
githubRepoOpened: false,
|
||||||
setGithubRepoOpened: emptyFn,
|
setGithubRepoOpened: emptyFn,
|
||||||
@@ -59,9 +52,6 @@ export const LocalConfigContext = createContext<LocalConfigContext>({
|
|||||||
starUsDialogLastOpen: 0,
|
starUsDialogLastOpen: 0,
|
||||||
setStarUsDialogLastOpen: emptyFn,
|
setStarUsDialogLastOpen: emptyFn,
|
||||||
|
|
||||||
showDependenciesOnCanvas: false,
|
|
||||||
setShowDependenciesOnCanvas: emptyFn,
|
|
||||||
|
|
||||||
showMiniMapOnCanvas: false,
|
showMiniMapOnCanvas: false,
|
||||||
setShowMiniMapOnCanvas: emptyFn,
|
setShowMiniMapOnCanvas: emptyFn,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import type { SchemasFilter, ScrollAction } from './local-config-context';
|
import type { ScrollAction } from './local-config-context';
|
||||||
import { LocalConfigContext } from './local-config-context';
|
import { LocalConfigContext } from './local-config-context';
|
||||||
import type { Theme } from '../theme-context/theme-context';
|
import type { Theme } from '../theme-context/theme-context';
|
||||||
|
|
||||||
const themeKey = 'theme';
|
const themeKey = 'theme';
|
||||||
const scrollActionKey = 'scroll_action';
|
const scrollActionKey = 'scroll_action';
|
||||||
const schemasFilterKey = 'schemas_filter';
|
|
||||||
const showCardinalityKey = 'show_cardinality';
|
const showCardinalityKey = 'show_cardinality';
|
||||||
const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification';
|
const showFieldAttributesKey = 'show_field_attributes';
|
||||||
const githubRepoOpenedKey = 'github_repo_opened';
|
const githubRepoOpenedKey = 'github_repo_opened';
|
||||||
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
const starUsDialogLastOpenKey = 'star_us_dialog_last_open';
|
||||||
const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas';
|
|
||||||
const showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
|
const showMiniMapOnCanvasKey = 'show_minimap_on_canvas';
|
||||||
|
const showDBViewsKey = 'show_db_views';
|
||||||
|
|
||||||
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -24,20 +23,17 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
|
(localStorage.getItem(scrollActionKey) as ScrollAction) || 'pan'
|
||||||
);
|
);
|
||||||
|
|
||||||
const [schemasFilter, setSchemasFilter] = React.useState<SchemasFilter>(
|
const [showDBViews, setShowDBViews] = React.useState<boolean>(
|
||||||
JSON.parse(
|
(localStorage.getItem(showDBViewsKey) || 'false') === 'true'
|
||||||
localStorage.getItem(schemasFilterKey) || '{}'
|
|
||||||
) as SchemasFilter
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showCardinality, setShowCardinality] = React.useState<boolean>(
|
const [showCardinality, setShowCardinality] = React.useState<boolean>(
|
||||||
(localStorage.getItem(showCardinalityKey) || 'true') === 'true'
|
(localStorage.getItem(showCardinalityKey) || 'true') === 'true'
|
||||||
);
|
);
|
||||||
|
|
||||||
const [hideMultiSchemaNotification, setHideMultiSchemaNotification] =
|
const [showFieldAttributes, setShowFieldAttributes] =
|
||||||
React.useState<boolean>(
|
React.useState<boolean>(
|
||||||
(localStorage.getItem(hideMultiSchemaNotificationKey) ||
|
(localStorage.getItem(showFieldAttributesKey) || 'true') === 'true'
|
||||||
'false') === 'true'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
|
const [githubRepoOpened, setGithubRepoOpened] = React.useState<boolean>(
|
||||||
@@ -49,12 +45,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
|
parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0')
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] =
|
|
||||||
React.useState<boolean>(
|
|
||||||
(localStorage.getItem(showDependenciesOnCanvasKey) || 'false') ===
|
|
||||||
'true'
|
|
||||||
);
|
|
||||||
|
|
||||||
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
|
const [showMiniMapOnCanvas, setShowMiniMapOnCanvas] =
|
||||||
React.useState<boolean>(
|
React.useState<boolean>(
|
||||||
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
|
(localStorage.getItem(showMiniMapOnCanvasKey) || 'true') === 'true'
|
||||||
@@ -71,13 +61,6 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString());
|
||||||
}, [githubRepoOpened]);
|
}, [githubRepoOpened]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem(
|
|
||||||
hideMultiSchemaNotificationKey,
|
|
||||||
hideMultiSchemaNotification.toString()
|
|
||||||
);
|
|
||||||
}, [hideMultiSchemaNotification]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(themeKey, theme);
|
localStorage.setItem(themeKey, theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
@@ -87,20 +70,13 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
}, [scrollAction]);
|
}, [scrollAction]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(schemasFilterKey, JSON.stringify(schemasFilter));
|
localStorage.setItem(showDBViewsKey, showDBViews.toString());
|
||||||
}, [schemasFilter]);
|
}, [showDBViews]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(showCardinalityKey, showCardinality.toString());
|
localStorage.setItem(showCardinalityKey, showCardinality.toString());
|
||||||
}, [showCardinality]);
|
}, [showCardinality]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem(
|
|
||||||
showDependenciesOnCanvasKey,
|
|
||||||
showDependenciesOnCanvas.toString()
|
|
||||||
);
|
|
||||||
}, [showDependenciesOnCanvas]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
showMiniMapOnCanvasKey,
|
showMiniMapOnCanvasKey,
|
||||||
@@ -115,18 +91,16 @@ export const LocalConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
setTheme,
|
setTheme,
|
||||||
scrollAction,
|
scrollAction,
|
||||||
setScrollAction,
|
setScrollAction,
|
||||||
schemasFilter,
|
showDBViews,
|
||||||
setSchemasFilter,
|
setShowDBViews,
|
||||||
showCardinality,
|
showCardinality,
|
||||||
setShowCardinality,
|
setShowCardinality,
|
||||||
hideMultiSchemaNotification,
|
showFieldAttributes,
|
||||||
setHideMultiSchemaNotification,
|
setShowFieldAttributes,
|
||||||
setGithubRepoOpened,
|
setGithubRepoOpened,
|
||||||
githubRepoOpened,
|
githubRepoOpened,
|
||||||
starUsDialogLastOpen,
|
starUsDialogLastOpen,
|
||||||
setStarUsDialogLastOpen,
|
setStarUsDialogLastOpen,
|
||||||
showDependenciesOnCanvas,
|
|
||||||
setShowDependenciesOnCanvas,
|
|
||||||
showMiniMapOnCanvas,
|
showMiniMapOnCanvas,
|
||||||
setShowMiniMapOnCanvas,
|
setShowMiniMapOnCanvas,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -7,12 +7,22 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
|||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import type { Area } from '@/lib/domain/area';
|
import type { Area } from '@/lib/domain/area';
|
||||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||||
|
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import type { Note } from '@/lib/domain/note';
|
||||||
|
|
||||||
export interface StorageContext {
|
export interface StorageContext {
|
||||||
// Config operations
|
// Config operations
|
||||||
getConfig: () => Promise<ChartDBConfig | undefined>;
|
getConfig: () => Promise<ChartDBConfig | undefined>;
|
||||||
updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>;
|
updateConfig: (config: Partial<ChartDBConfig>) => Promise<void>;
|
||||||
|
|
||||||
|
// Diagram filter operations
|
||||||
|
getDiagramFilter: (diagramId: string) => Promise<DiagramFilter | undefined>;
|
||||||
|
updateDiagramFilter: (
|
||||||
|
diagramId: string,
|
||||||
|
filter: DiagramFilter
|
||||||
|
) => Promise<void>;
|
||||||
|
deleteDiagramFilter: (diagramId: string) => Promise<void>;
|
||||||
|
|
||||||
// Diagram operations
|
// Diagram operations
|
||||||
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
||||||
listDiagrams: (options?: {
|
listDiagrams: (options?: {
|
||||||
@@ -21,6 +31,7 @@ export interface StorageContext {
|
|||||||
includeDependencies?: boolean;
|
includeDependencies?: boolean;
|
||||||
includeAreas?: boolean;
|
includeAreas?: boolean;
|
||||||
includeCustomTypes?: boolean;
|
includeCustomTypes?: boolean;
|
||||||
|
includeNotes?: boolean;
|
||||||
}) => Promise<Diagram[]>;
|
}) => Promise<Diagram[]>;
|
||||||
getDiagram: (
|
getDiagram: (
|
||||||
id: string,
|
id: string,
|
||||||
@@ -30,6 +41,7 @@ export interface StorageContext {
|
|||||||
includeDependencies?: boolean;
|
includeDependencies?: boolean;
|
||||||
includeAreas?: boolean;
|
includeAreas?: boolean;
|
||||||
includeCustomTypes?: boolean;
|
includeCustomTypes?: boolean;
|
||||||
|
includeNotes?: boolean;
|
||||||
}
|
}
|
||||||
) => Promise<Diagram | undefined>;
|
) => Promise<Diagram | undefined>;
|
||||||
updateDiagram: (params: {
|
updateDiagram: (params: {
|
||||||
@@ -126,12 +138,30 @@ export interface StorageContext {
|
|||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
listCustomTypes: (diagramId: string) => Promise<DBCustomType[]>;
|
listCustomTypes: (diagramId: string) => Promise<DBCustomType[]>;
|
||||||
deleteDiagramCustomTypes: (diagramId: string) => Promise<void>;
|
deleteDiagramCustomTypes: (diagramId: string) => Promise<void>;
|
||||||
|
|
||||||
|
// Note operations
|
||||||
|
addNote: (params: { diagramId: string; note: Note }) => Promise<void>;
|
||||||
|
getNote: (params: {
|
||||||
|
diagramId: string;
|
||||||
|
id: string;
|
||||||
|
}) => Promise<Note | undefined>;
|
||||||
|
updateNote: (params: {
|
||||||
|
id: string;
|
||||||
|
attributes: Partial<Note>;
|
||||||
|
}) => Promise<void>;
|
||||||
|
deleteNote: (params: { diagramId: string; id: string }) => Promise<void>;
|
||||||
|
listNotes: (diagramId: string) => Promise<Note[]>;
|
||||||
|
deleteDiagramNotes: (diagramId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storageInitialValue: StorageContext = {
|
export const storageInitialValue: StorageContext = {
|
||||||
getConfig: emptyFn,
|
getConfig: emptyFn,
|
||||||
updateConfig: emptyFn,
|
updateConfig: emptyFn,
|
||||||
|
|
||||||
|
getDiagramFilter: emptyFn,
|
||||||
|
updateDiagramFilter: emptyFn,
|
||||||
|
deleteDiagramFilter: emptyFn,
|
||||||
|
|
||||||
addDiagram: emptyFn,
|
addDiagram: emptyFn,
|
||||||
listDiagrams: emptyFn,
|
listDiagrams: emptyFn,
|
||||||
getDiagram: emptyFn,
|
getDiagram: emptyFn,
|
||||||
@@ -174,6 +204,14 @@ export const storageInitialValue: StorageContext = {
|
|||||||
deleteCustomType: emptyFn,
|
deleteCustomType: emptyFn,
|
||||||
listCustomTypes: emptyFn,
|
listCustomTypes: emptyFn,
|
||||||
deleteDiagramCustomTypes: emptyFn,
|
deleteDiagramCustomTypes: emptyFn,
|
||||||
|
|
||||||
|
// Note operations
|
||||||
|
addNote: emptyFn,
|
||||||
|
getNote: emptyFn,
|
||||||
|
updateNote: emptyFn,
|
||||||
|
deleteNote: emptyFn,
|
||||||
|
listNotes: emptyFn,
|
||||||
|
deleteDiagramNotes: emptyFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const storageContext =
|
export const storageContext =
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import type { ChartDBConfig } from '@/lib/domain/config';
|
|||||||
import type { DBDependency } from '@/lib/domain/db-dependency';
|
import type { DBDependency } from '@/lib/domain/db-dependency';
|
||||||
import type { Area } from '@/lib/domain/area';
|
import type { Area } from '@/lib/domain/area';
|
||||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||||
|
import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
|
||||||
|
import type { Note } from '@/lib/domain/note';
|
||||||
|
|
||||||
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
@@ -40,10 +42,18 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
DBCustomType & { diagramId: string },
|
DBCustomType & { diagramId: string },
|
||||||
'id' // primary key "id" (for the typings only)
|
'id' // primary key "id" (for the typings only)
|
||||||
>;
|
>;
|
||||||
|
notes: EntityTable<
|
||||||
|
Note & { diagramId: string },
|
||||||
|
'id' // primary key "id" (for the typings only)
|
||||||
|
>;
|
||||||
config: EntityTable<
|
config: EntityTable<
|
||||||
ChartDBConfig & { id: number },
|
ChartDBConfig & { id: number },
|
||||||
'id' // primary key "id" (for the typings only)
|
'id' // primary key "id" (for the typings only)
|
||||||
>;
|
>;
|
||||||
|
diagram_filters: EntityTable<
|
||||||
|
DiagramFilter & { diagramId: string },
|
||||||
|
'diagramId' // primary key "id" (for the typings only)
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Schema declaration:
|
// Schema declaration:
|
||||||
@@ -190,6 +200,44 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
config: '++id, defaultDiagramId',
|
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.version(13).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',
|
||||||
|
notes: '++id, diagramId, content, x, y, width, height, color',
|
||||||
|
});
|
||||||
|
|
||||||
dexieDB.on('ready', async () => {
|
dexieDB.on('ready', async () => {
|
||||||
const config = await dexieDB.config.get(1);
|
const config = await dexieDB.config.get(1);
|
||||||
|
|
||||||
@@ -217,6 +265,34 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[db]
|
[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(
|
const addTable: StorageContext['addTable'] = useCallback(
|
||||||
async ({ diagramId, table }) => {
|
async ({ diagramId, table }) => {
|
||||||
await db.db_tables.add({
|
await db.db_tables.add({
|
||||||
@@ -496,6 +572,56 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
[db]
|
[db]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Note operations
|
||||||
|
const addNote: StorageContext['addNote'] = useCallback(
|
||||||
|
async ({ note, diagramId }) => {
|
||||||
|
await db.notes.add({
|
||||||
|
...note,
|
||||||
|
diagramId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getNote: StorageContext['getNote'] = useCallback(
|
||||||
|
async ({ diagramId, id }) => {
|
||||||
|
return await db.notes.get({ id, diagramId });
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateNote: StorageContext['updateNote'] = useCallback(
|
||||||
|
async ({ id, attributes }) => {
|
||||||
|
await db.notes.update(id, attributes);
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteNote: StorageContext['deleteNote'] = useCallback(
|
||||||
|
async ({ diagramId, id }) => {
|
||||||
|
await db.notes.where({ id, diagramId }).delete();
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
|
const listNotes: StorageContext['listNotes'] = useCallback(
|
||||||
|
async (diagramId) => {
|
||||||
|
return await db.notes
|
||||||
|
.where('diagramId')
|
||||||
|
.equals(diagramId)
|
||||||
|
.toArray();
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteDiagramNotes: StorageContext['deleteDiagramNotes'] =
|
||||||
|
useCallback(
|
||||||
|
async (diagramId) => {
|
||||||
|
await db.notes.where('diagramId').equals(diagramId).delete();
|
||||||
|
},
|
||||||
|
[db]
|
||||||
|
);
|
||||||
|
|
||||||
const addDiagram: StorageContext['addDiagram'] = useCallback(
|
const addDiagram: StorageContext['addDiagram'] = useCallback(
|
||||||
async ({ diagram }) => {
|
async ({ diagram }) => {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
@@ -543,9 +669,22 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const notes = diagram.notes ?? [];
|
||||||
|
promises.push(
|
||||||
|
...notes.map((note) => addNote({ diagramId: diagram.id, note }))
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
},
|
},
|
||||||
[db, addArea, addCustomType, addDependency, addRelationship, addTable]
|
[
|
||||||
|
db,
|
||||||
|
addArea,
|
||||||
|
addCustomType,
|
||||||
|
addDependency,
|
||||||
|
addRelationship,
|
||||||
|
addTable,
|
||||||
|
addNote,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const listDiagrams: StorageContext['listDiagrams'] = useCallback(
|
const listDiagrams: StorageContext['listDiagrams'] = useCallback(
|
||||||
@@ -556,6 +695,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
includeDependencies: false,
|
includeDependencies: false,
|
||||||
includeAreas: false,
|
includeAreas: false,
|
||||||
includeCustomTypes: false,
|
includeCustomTypes: false,
|
||||||
|
includeNotes: false,
|
||||||
}
|
}
|
||||||
): Promise<Diagram[]> => {
|
): Promise<Diagram[]> => {
|
||||||
let diagrams = await db.diagrams.toArray();
|
let diagrams = await db.diagrams.toArray();
|
||||||
@@ -609,6 +749,15 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.includeNotes) {
|
||||||
|
diagrams = await Promise.all(
|
||||||
|
diagrams.map(async (diagram) => {
|
||||||
|
diagram.notes = await listNotes(diagram.id);
|
||||||
|
return diagram;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return diagrams;
|
return diagrams;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -618,6 +767,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
listDependencies,
|
listDependencies,
|
||||||
listRelationships,
|
listRelationships,
|
||||||
listTables,
|
listTables,
|
||||||
|
listNotes,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -630,6 +780,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
includeDependencies: false,
|
includeDependencies: false,
|
||||||
includeAreas: false,
|
includeAreas: false,
|
||||||
includeCustomTypes: false,
|
includeCustomTypes: false,
|
||||||
|
includeNotes: false,
|
||||||
}
|
}
|
||||||
): Promise<Diagram | undefined> => {
|
): Promise<Diagram | undefined> => {
|
||||||
const diagram = await db.diagrams.get(id);
|
const diagram = await db.diagrams.get(id);
|
||||||
@@ -658,6 +809,10 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
diagram.customTypes = await listCustomTypes(id);
|
diagram.customTypes = await listCustomTypes(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.includeNotes) {
|
||||||
|
diagram.notes = await listNotes(id);
|
||||||
|
}
|
||||||
|
|
||||||
return diagram;
|
return diagram;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -667,6 +822,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
listDependencies,
|
listDependencies,
|
||||||
listRelationships,
|
listRelationships,
|
||||||
listTables,
|
listTables,
|
||||||
|
listNotes,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -695,6 +851,9 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
.where('diagramId')
|
.where('diagramId')
|
||||||
.equals(id)
|
.equals(id)
|
||||||
.modify({ diagramId: attributes.id }),
|
.modify({ diagramId: attributes.id }),
|
||||||
|
db.notes.where('diagramId').equals(id).modify({
|
||||||
|
diagramId: attributes.id,
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -710,6 +869,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
db.db_dependencies.where('diagramId').equals(id).delete(),
|
db.db_dependencies.where('diagramId').equals(id).delete(),
|
||||||
db.areas.where('diagramId').equals(id).delete(),
|
db.areas.where('diagramId').equals(id).delete(),
|
||||||
db.db_custom_types.where('diagramId').equals(id).delete(),
|
db.db_custom_types.where('diagramId').equals(id).delete(),
|
||||||
|
db.notes.where('diagramId').equals(id).delete(),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
[db]
|
[db]
|
||||||
@@ -756,6 +916,15 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
deleteCustomType,
|
deleteCustomType,
|
||||||
listCustomTypes,
|
listCustomTypes,
|
||||||
deleteDiagramCustomTypes,
|
deleteDiagramCustomTypes,
|
||||||
|
addNote,
|
||||||
|
getNote,
|
||||||
|
updateNote,
|
||||||
|
deleteNote,
|
||||||
|
listNotes,
|
||||||
|
deleteDiagramNotes,
|
||||||
|
getDiagramFilter,
|
||||||
|
updateDiagramFilter,
|
||||||
|
deleteDiagramFilter,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const ThemeProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
handleThemeToggle,
|
handleThemeToggle,
|
||||||
{
|
{
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
|
enableOnFormTags: true,
|
||||||
},
|
},
|
||||||
[handleThemeToggle]
|
[handleThemeToggle]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,58 +35,34 @@ import type { OnChange } from '@monaco-editor/react';
|
|||||||
import { useDebounce } from '@/hooks/use-debounce-v2';
|
import { useDebounce } from '@/hooks/use-debounce-v2';
|
||||||
import { InstructionsSection } from './instructions-section/instructions-section';
|
import { InstructionsSection } from './instructions-section/instructions-section';
|
||||||
import { parseSQLError } from '@/lib/data/sql-import';
|
import { parseSQLError } from '@/lib/data/sql-import';
|
||||||
import type * as monaco from 'monaco-editor';
|
import type { editor, IDisposable } from 'monaco-editor';
|
||||||
import { waitFor } from '@/lib/utils';
|
import { waitFor } from '@/lib/utils';
|
||||||
import { type ValidationResult } from '@/lib/data/sql-import/sql-validator';
|
import {
|
||||||
import { validateSQL } from '@/lib/data/sql-import/unified-sql-validator';
|
validateSQL,
|
||||||
|
type ValidationResult,
|
||||||
|
} from '@/lib/data/sql-import/sql-validator';
|
||||||
import { SQLValidationStatus } from './sql-validation-status';
|
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 =
|
const errorScriptOutputMessage =
|
||||||
'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.';
|
'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.';
|
||||||
|
|
||||||
// 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 {
|
export interface ImportDatabaseProps {
|
||||||
goBack?: () => void;
|
goBack?: () => void;
|
||||||
onImport: () => void;
|
onImport: () => void;
|
||||||
@@ -100,8 +76,8 @@ export interface ImportDatabaseProps {
|
|||||||
>;
|
>;
|
||||||
keepDialogAfterImport?: boolean;
|
keepDialogAfterImport?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
importMethod: 'query' | 'ddl';
|
importMethod: ImportMethod;
|
||||||
setImportMethod: (method: 'query' | 'ddl') => void;
|
setImportMethod: (method: ImportMethod) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
||||||
@@ -120,8 +96,9 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { effectiveTheme } = useTheme();
|
const { effectiveTheme } = useTheme();
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||||
const pasteDisposableRef = useRef<monaco.IDisposable | null>(null);
|
const decorationsCollection = useRef<editor.IEditorDecorationsCollection>();
|
||||||
|
const pasteDisposableRef = useRef<IDisposable | null>(null);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isSm: isDesktop } = useBreakpoint('sm');
|
const { isSm: isDesktop } = useBreakpoint('sm');
|
||||||
@@ -135,15 +112,20 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
const [isAutoFixing, setIsAutoFixing] = useState(false);
|
const [isAutoFixing, setIsAutoFixing] = useState(false);
|
||||||
const [showAutoFixButton, setShowAutoFixButton] = useState(false);
|
const [showAutoFixButton, setShowAutoFixButton] = useState(false);
|
||||||
|
|
||||||
|
const clearDecorations = useCallback(() => {
|
||||||
|
clearErrorHighlight(decorationsCollection.current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setScriptResult('');
|
setScriptResult('');
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
setShowCheckJsonButton(false);
|
setShowCheckJsonButton(false);
|
||||||
}, [importMethod, setScriptResult]);
|
}, [importMethod, setScriptResult]);
|
||||||
|
|
||||||
// Check if the ddl is valid
|
// Check if the ddl or dbml is valid
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (importMethod !== 'ddl') {
|
clearDecorations();
|
||||||
|
if (importMethod === 'query') {
|
||||||
setSqlValidation(null);
|
setSqlValidation(null);
|
||||||
setShowAutoFixButton(false);
|
setShowAutoFixButton(false);
|
||||||
return;
|
return;
|
||||||
@@ -152,9 +134,54 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
if (!scriptResult.trim()) {
|
if (!scriptResult.trim()) {
|
||||||
setSqlValidation(null);
|
setSqlValidation(null);
|
||||||
setShowAutoFixButton(false);
|
setShowAutoFixButton(false);
|
||||||
|
setErrorMessage('');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (importMethod === 'dbml') {
|
||||||
|
// Validate DBML by parsing it
|
||||||
|
const validateResponse = verifyDBML(scriptResult, { databaseType });
|
||||||
|
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
|
// First run our validation based on database type
|
||||||
const validation = validateSQL(scriptResult, databaseType);
|
const validation = validateSQL(scriptResult, databaseType);
|
||||||
setSqlValidation(validation);
|
setSqlValidation(validation);
|
||||||
@@ -181,7 +208,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
setErrorMessage(result.error);
|
setErrorMessage(result.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [importMethod, scriptResult, databaseType]);
|
}, [importMethod, scriptResult, databaseType, clearDecorations]);
|
||||||
|
|
||||||
// Check if the script result is a valid JSON
|
// Check if the script result is a valid JSON
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -220,11 +247,15 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
if (sqlValidation?.fixedSQL) {
|
if (sqlValidation?.fixedSQL) {
|
||||||
setIsAutoFixing(true);
|
setIsAutoFixing(true);
|
||||||
setShowAutoFixButton(false);
|
setShowAutoFixButton(false);
|
||||||
|
setErrorMessage('');
|
||||||
|
|
||||||
// Apply the fix with a delay so user sees the fixing message
|
// Apply the fix with a delay so user sees the fixing message
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setScriptResult(sqlValidation.fixedSQL!);
|
setScriptResult(sqlValidation.fixedSQL!);
|
||||||
setIsAutoFixing(false);
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsAutoFixing(false);
|
||||||
|
}, 100);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}, [sqlValidation, setScriptResult]);
|
}, [sqlValidation, setScriptResult]);
|
||||||
@@ -240,6 +271,16 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
|
|
||||||
const formatEditor = useCallback(() => {
|
const formatEditor = useCallback(() => {
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
|
const model = editorRef.current.getModel();
|
||||||
|
if (model) {
|
||||||
|
const content = model.getValue();
|
||||||
|
|
||||||
|
// Skip formatting for large files (> 2MB)
|
||||||
|
if (calculateIsLargeFile(content)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editorRef.current
|
editorRef.current
|
||||||
?.getAction('editor.action.formatDocument')
|
?.getAction('editor.action.formatDocument')
|
||||||
@@ -293,8 +334,10 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleEditorDidMount = useCallback(
|
const handleEditorDidMount = useCallback(
|
||||||
(editor: monaco.editor.IStandaloneCodeEditor) => {
|
(editor: editor.IStandaloneCodeEditor) => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
decorationsCollection.current =
|
||||||
|
editor.createDecorationsCollection();
|
||||||
|
|
||||||
// Cleanup previous disposable if it exists
|
// Cleanup previous disposable if it exists
|
||||||
if (pasteDisposableRef.current) {
|
if (pasteDisposableRef.current) {
|
||||||
@@ -309,14 +352,17 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
|
|
||||||
const content = model.getValue();
|
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
|
// First, detect content type to determine if we should switch modes
|
||||||
const detectedType = detectContentType(content);
|
const detectedType = detectImportMethod(content);
|
||||||
if (detectedType && detectedType !== importMethod) {
|
if (detectedType && detectedType !== importMethod) {
|
||||||
// Switch to the detected mode immediately
|
// Switch to the detected mode immediately
|
||||||
setImportMethod(detectedType);
|
setImportMethod(detectedType);
|
||||||
|
|
||||||
// Only format if it's JSON (query mode)
|
// Only format if it's JSON (query mode) AND file is not too large
|
||||||
if (detectedType === 'query') {
|
if (detectedType === 'query' && !isLargeFile) {
|
||||||
// For JSON mode, format after a short delay
|
// For JSON mode, format after a short delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editor
|
editor
|
||||||
@@ -324,18 +370,18 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
?.run();
|
?.run();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
// For DDL mode, do NOT format as it can break the SQL
|
// For DDL and DBML modes, do NOT format as it can break the syntax
|
||||||
} else {
|
} else {
|
||||||
// Content type didn't change, apply formatting based on current mode
|
// Content type didn't change, apply formatting based on current mode
|
||||||
if (importMethod === 'query') {
|
if (importMethod === 'query' && !isLargeFile) {
|
||||||
// Only format JSON content
|
// Only format JSON content if not too large
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editor
|
editor
|
||||||
.getAction('editor.action.formatDocument')
|
.getAction('editor.action.formatDocument')
|
||||||
?.run();
|
?.run();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
// For DDL mode, do NOT format
|
// For DDL and DBML modes or large files, do NOT format
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -382,16 +428,25 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
<div className="w-full text-center text-xs text-muted-foreground">
|
<div className="w-full text-center text-xs text-muted-foreground">
|
||||||
{importMethod === 'query'
|
{importMethod === 'query'
|
||||||
? 'Smart Query Output'
|
? 'Smart Query Output'
|
||||||
: 'SQL Script'}
|
: importMethod === 'dbml'
|
||||||
|
? 'DBML Script'
|
||||||
|
: 'SQL Script'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<Suspense fallback={<Spinner />}>
|
<Suspense fallback={<Spinner />}>
|
||||||
<Editor
|
<Editor
|
||||||
value={scriptResult}
|
value={scriptResult}
|
||||||
onChange={debouncedHandleInputChange}
|
onChange={debouncedHandleInputChange}
|
||||||
language={importMethod === 'query' ? 'json' : 'sql'}
|
language={
|
||||||
|
importMethod === 'query'
|
||||||
|
? 'json'
|
||||||
|
: importMethod === 'dbml'
|
||||||
|
? 'dbml'
|
||||||
|
: 'sql'
|
||||||
|
}
|
||||||
loading={<Spinner />}
|
loading={<Spinner />}
|
||||||
onMount={handleEditorDidMount}
|
onMount={handleEditorDidMount}
|
||||||
|
beforeMount={setupDBMLLanguage}
|
||||||
theme={
|
theme={
|
||||||
effectiveTheme === 'dark'
|
effectiveTheme === 'dark'
|
||||||
? 'dbml-dark'
|
? 'dbml-dark'
|
||||||
@@ -402,7 +457,6 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
minimap: { enabled: false },
|
minimap: { enabled: false },
|
||||||
scrollBeyondLastLine: false,
|
scrollBeyondLastLine: false,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
glyphMargin: false,
|
|
||||||
lineNumbers: 'on',
|
lineNumbers: 'on',
|
||||||
guides: {
|
guides: {
|
||||||
indentation: false,
|
indentation: false,
|
||||||
@@ -427,21 +481,15 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{errorMessage || (importMethod === 'ddl' && sqlValidation) ? (
|
{errorMessage ||
|
||||||
importMethod === 'ddl' ? (
|
((importMethod === 'ddl' || importMethod === 'dbml') &&
|
||||||
<SQLValidationStatus
|
sqlValidation) ? (
|
||||||
validation={sqlValidation}
|
<SQLValidationStatus
|
||||||
errorMessage={errorMessage}
|
validation={sqlValidation}
|
||||||
isAutoFixing={isAutoFixing}
|
errorMessage={errorMessage}
|
||||||
onErrorClick={handleErrorClick}
|
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}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -546,7 +594,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={handleAutoFix}
|
onClick={handleAutoFix}
|
||||||
disabled={isAutoFixing}
|
disabled={isAutoFixing}
|
||||||
className="bg-blue-600 text-white hover:bg-blue-700"
|
className="bg-sky-600 text-white hover:bg-sky-700"
|
||||||
>
|
>
|
||||||
{isAutoFixing ? (
|
{isAutoFixing ? (
|
||||||
<Spinner size="small" />
|
<Spinner size="small" />
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ import {
|
|||||||
AvatarImage,
|
AvatarImage,
|
||||||
} from '@/components/avatar/avatar';
|
} from '@/components/avatar/avatar';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Code } from 'lucide-react';
|
import { Code, FileCode } from 'lucide-react';
|
||||||
import { SmartQueryInstructions } from './instructions/smart-query-instructions';
|
import { SmartQueryInstructions } from './instructions/smart-query-instructions';
|
||||||
import { DDLInstructions } from './instructions/ddl-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[] = [
|
const DatabasesWithoutDDLInstructions: DatabaseType[] = [
|
||||||
DatabaseType.CLICKHOUSE,
|
DatabaseType.CLICKHOUSE,
|
||||||
@@ -30,8 +32,8 @@ export interface InstructionsSectionProps {
|
|||||||
setDatabaseEdition: React.Dispatch<
|
setDatabaseEdition: React.Dispatch<
|
||||||
React.SetStateAction<DatabaseEdition | undefined>
|
React.SetStateAction<DatabaseEdition | undefined>
|
||||||
>;
|
>;
|
||||||
importMethod: 'query' | 'ddl';
|
importMethod: ImportMethod;
|
||||||
setImportMethod: (method: 'query' | 'ddl') => void;
|
setImportMethod: (method: ImportMethod) => void;
|
||||||
showSSMSInfoDialog: boolean;
|
showSSMSInfoDialog: boolean;
|
||||||
setShowSSMSInfoDialog: (show: boolean) => void;
|
setShowSSMSInfoDialog: (show: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -115,48 +117,60 @@ export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{DatabasesWithoutDDLInstructions.includes(databaseType) ? null : (
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex flex-col gap-1">
|
<p className="text-sm leading-6 text-primary">
|
||||||
<p className="text-sm leading-6 text-primary">
|
How would you like to import?
|
||||||
How would you like to import?
|
</p>
|
||||||
</p>
|
<ToggleGroup
|
||||||
<ToggleGroup
|
type="single"
|
||||||
type="single"
|
className="ml-1 flex-wrap justify-start gap-2"
|
||||||
className="ml-1 flex-wrap justify-start gap-2"
|
value={importMethod}
|
||||||
value={importMethod}
|
onValueChange={(value) => {
|
||||||
onValueChange={(value) => {
|
let selectedImportMethod: ImportMethod = 'query';
|
||||||
let selectedImportMethod: 'query' | 'ddl' = 'query';
|
if (value) {
|
||||||
if (value) {
|
selectedImportMethod = value as ImportMethod;
|
||||||
selectedImportMethod = value as 'query' | 'ddl';
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setImportMethod(selectedImportMethod);
|
setImportMethod(selectedImportMethod);
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="query"
|
||||||
|
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"
|
||||||
>
|
>
|
||||||
<ToggleGroupItem
|
<Avatar className="h-3 w-4 rounded-none">
|
||||||
value="query"
|
<AvatarImage src={logo} alt="query" />
|
||||||
variant="outline"
|
<AvatarFallback>Query</AvatarFallback>
|
||||||
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>
|
||||||
>
|
Smart Query
|
||||||
<Avatar className="h-3 w-4 rounded-none">
|
</ToggleGroupItem>
|
||||||
<AvatarImage src={logo} alt="query" />
|
{!DatabasesWithoutDDLInstructions.includes(
|
||||||
<AvatarFallback>Query</AvatarFallback>
|
databaseType
|
||||||
</Avatar>
|
) && (
|
||||||
Smart Query
|
|
||||||
</ToggleGroupItem>
|
|
||||||
<ToggleGroupItem
|
<ToggleGroupItem
|
||||||
value="ddl"
|
value="ddl"
|
||||||
variant="outline"
|
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"
|
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">
|
<Avatar className="size-4 rounded-none">
|
||||||
<Code size={16} />
|
<FileCode size={16} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
SQL Script
|
SQL Script
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</ToggleGroup>
|
)}
|
||||||
</div>
|
<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
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="text-sm font-semibold">Instructions:</div>
|
<div className="text-sm font-semibold">Instructions:</div>
|
||||||
@@ -167,11 +181,16 @@ export const InstructionsSection: React.FC<InstructionsSectionProps> = ({
|
|||||||
showSSMSInfoDialog={showSSMSInfoDialog}
|
showSSMSInfoDialog={showSSMSInfoDialog}
|
||||||
setShowSSMSInfoDialog={setShowSSMSInfoDialog}
|
setShowSSMSInfoDialog={setShowSSMSInfoDialog}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : importMethod === 'ddl' ? (
|
||||||
<DDLInstructions
|
<DDLInstructions
|
||||||
databaseType={databaseType}
|
databaseType={databaseType}
|
||||||
databaseEdition={databaseEdition}
|
databaseEdition={databaseEdition}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<DBMLInstructions
|
||||||
|
databaseType={databaseType}
|
||||||
|
databaseEdition={databaseEdition}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -43,8 +43,8 @@ const DDLInstructionsMap: Record<DatabaseType, DDLInstruction[]> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Execute the following command in your terminal:',
|
text: 'Execute the following command in your terminal:',
|
||||||
code: `sqlite3 <database_file_path>\n.dump > <output_file_path>`,
|
code: `sqlite3 <database_file_path>\n".schema" > <output_file_path>`,
|
||||||
example: `sqlite3 my_db.db\n.dump > schema_export.sql`,
|
example: `sqlite3 my_db.db\n".schema" > schema_export.sql`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Open the exported SQL file, copy its contents, and paste them here.',
|
text: 'Open the exported SQL file, copy its contents, and paste them here.',
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import {
|
import { CheckCircle, AlertTriangle, MessageCircleWarning } from 'lucide-react';
|
||||||
AlertCircle,
|
|
||||||
CheckCircle,
|
|
||||||
AlertTriangle,
|
|
||||||
Lightbulb,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { Alert, AlertDescription } from '@/components/alert/alert';
|
import { Alert, AlertDescription } from '@/components/alert/alert';
|
||||||
import type { ValidationResult } from '@/lib/data/sql-import/sql-validator';
|
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 {
|
interface SQLValidationStatusProps {
|
||||||
validation: ValidationResult | null;
|
validation?: ValidationResult | null;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
isAutoFixing?: boolean;
|
isAutoFixing?: boolean;
|
||||||
onErrorClick?: (line: number) => void;
|
onErrorClick?: (line: number) => void;
|
||||||
@@ -21,71 +19,113 @@ export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
|
|||||||
isAutoFixing = false,
|
isAutoFixing = false,
|
||||||
onErrorClick,
|
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 (!validation && !errorMessage && !isAutoFixing) return null;
|
||||||
|
|
||||||
const hasErrors = validation?.errors && validation.errors.length > 0;
|
if (isAutoFixing) {
|
||||||
const hasWarnings = validation?.warnings && validation.warnings.length > 0;
|
return (
|
||||||
const wasAutoFixed =
|
<>
|
||||||
validation?.warnings?.some((w) => w.message.includes('Auto-fixed')) ||
|
<Separator className="mb-1 mt-2" />
|
||||||
false;
|
<div className="rounded-md border border-sky-200 bg-sky-50 dark:border-sky-800 dark:bg-sky-950">
|
||||||
|
<div className="space-y-3 p-3 pt-2 text-sky-700 dark:text-sky-300">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<Spinner className="mt-0.5 size-4 shrink-0 text-sky-700 dark:text-sky-300" />
|
||||||
|
<div className="flex-1 text-sm text-sky-700 dark:text-sky-300">
|
||||||
|
Auto-fixing SQL syntax errors...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If we have parser errors (errorMessage) after validation
|
// If we have parser errors (errorMessage) after validation
|
||||||
if (errorMessage && !hasErrors) {
|
if (errorMessage && !hasErrors) {
|
||||||
return (
|
return (
|
||||||
<Alert variant="destructive" className="mt-2">
|
<>
|
||||||
<AlertCircle className="size-4" />
|
<Separator className="mb-1 mt-2" />
|
||||||
<AlertDescription className="text-sm">
|
<div className="mb-1 flex shrink-0 items-center gap-2">
|
||||||
{errorMessage}
|
<p className="text-xs text-red-700">{errorMessage}</p>
|
||||||
</AlertDescription>
|
</div>
|
||||||
</Alert>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 space-y-2">
|
<>
|
||||||
{isAutoFixing && (
|
<Separator className="mb-1 mt-2" />
|
||||||
<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 && !isAutoFixing && (
|
{hasErrors ? (
|
||||||
<Alert variant="destructive">
|
<div className="rounded-md border border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950">
|
||||||
<AlertCircle className="size-4" />
|
<ScrollArea className="h-fit max-h-24">
|
||||||
<AlertDescription className="space-y-1 text-sm">
|
<div className="space-y-3 p-3 pt-2 text-red-700 dark:text-red-300">
|
||||||
<div className="font-medium">SQL Syntax Errors:</div>
|
{validation?.errors
|
||||||
{validation.errors.slice(0, 3).map((error, idx) => (
|
.slice(0, 3)
|
||||||
<div key={idx} className="ml-2">
|
.map((error, idx) => (
|
||||||
•{' '}
|
<div
|
||||||
<button
|
key={idx}
|
||||||
onClick={() => onErrorClick?.(error.line)}
|
className="flex items-start gap-2"
|
||||||
className="rounded underline hover:text-red-600 focus:outline-none focus:ring-1 focus:ring-red-500"
|
>
|
||||||
type="button"
|
<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">
|
||||||
Line {error.line}
|
<button
|
||||||
</button>
|
onClick={() =>
|
||||||
: {error.message}
|
onErrorClick?.(error.line)
|
||||||
{error.suggestion && (
|
}
|
||||||
<div className="ml-4 text-xs opacity-80">
|
className="rounded font-medium underline hover:text-red-600 focus:outline-none focus:ring-1 focus:ring-red-500 dark:hover:text-red-200"
|
||||||
→ {error.suggestion}
|
type="button"
|
||||||
|
>
|
||||||
|
Line {error.line}
|
||||||
|
</button>
|
||||||
|
<span className="mx-1">:</span>
|
||||||
|
<span className="text-xs">
|
||||||
|
{error.message}
|
||||||
|
</span>
|
||||||
|
{error.suggestion && (
|
||||||
|
<div className="mt-1 flex items-start gap-2">
|
||||||
|
<span className="text-xs font-medium ">
|
||||||
|
{error.suggestion}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
</div>
|
{validation?.errors &&
|
||||||
))}
|
validation?.errors.length > 3 ? (
|
||||||
{validation.errors.length > 3 && (
|
<div className="flex items-center gap-2">
|
||||||
<div className="ml-2 text-xs opacity-70">
|
<MessageCircleWarning className="mt-0.5 size-4 shrink-0 text-red-700 dark:text-red-300" />
|
||||||
... and {validation.errors.length - 3} more
|
<span className="text-xs font-medium">
|
||||||
errors
|
{validation.errors.length - 3} more
|
||||||
</div>
|
error
|
||||||
)}
|
{validation.errors.length - 3 > 1
|
||||||
</AlertDescription>
|
? 's'
|
||||||
</Alert>
|
: ''}
|
||||||
)}
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{wasAutoFixed && !hasErrors && (
|
{wasAutoFixed && !hasErrors ? (
|
||||||
<Alert className="border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950">
|
<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" />
|
<CheckCircle className="size-4 text-green-600 dark:text-green-400" />
|
||||||
<AlertDescription className="text-sm text-green-700 dark:text-green-300">
|
<AlertDescription className="text-sm text-green-700 dark:text-green-300">
|
||||||
@@ -93,30 +133,47 @@ export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
|
|||||||
now ready to import.
|
now ready to import.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
{hasWarnings && !hasErrors && (
|
{hasWarnings && !hasErrors ? (
|
||||||
<Alert>
|
<div className="rounded-md border border-sky-200 bg-sky-50 dark:border-sky-800 dark:bg-sky-950">
|
||||||
<AlertTriangle className="size-4" />
|
<ScrollArea className="h-fit max-h-24">
|
||||||
<AlertDescription className="space-y-1 text-sm">
|
<div className="space-y-3 p-3 pt-2 text-sky-700 dark:text-sky-300">
|
||||||
<div className="font-medium">Import Info:</div>
|
<div className="flex items-start gap-2">
|
||||||
{validation.warnings.map((warning, idx) => (
|
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-sky-700 dark:text-sky-300" />
|
||||||
<div key={idx} className="ml-2">
|
<div className="flex-1 text-sm text-sky-700 dark:text-sky-300">
|
||||||
• {warning.message}
|
<div className="mb-1 font-medium">
|
||||||
|
Import Info:
|
||||||
|
</div>
|
||||||
|
{validation?.warnings.map(
|
||||||
|
(warning, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="ml-2 text-xs"
|
||||||
|
>
|
||||||
|
• {warning.message}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</AlertDescription>
|
</ScrollArea>
|
||||||
</Alert>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
{!hasErrors && !hasWarnings && !errorMessage && validation && (
|
{!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">
|
<div className="rounded-md border 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" />
|
<div className="space-y-3 p-3 pt-2 text-green-700 dark:text-green-300">
|
||||||
<AlertDescription className="text-sm text-green-700 dark:text-green-300">
|
<div className="flex items-start gap-2">
|
||||||
SQL syntax validated successfully
|
<CheckCircle className="mt-0.5 size-4 shrink-0 text-green-700 dark:text-green-300" />
|
||||||
</AlertDescription>
|
<div className="flex-1 text-sm text-green-700 dark:text-green-300">
|
||||||
</Alert>
|
SQL syntax validated successfully
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
2
src/dialogs/common/select-tables/constants.ts
Normal file
2
src/dialogs/common/select-tables/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const MAX_TABLES_IN_DIAGRAM = 500;
|
||||||
|
export const MAX_TABLES_WITHOUT_SHOWING_FILTER = 50;
|
||||||
683
src/dialogs/common/select-tables/select-tables.tsx
Normal file
683
src/dialogs/common/select-tables/select-tables.tsx
Normal file
@@ -0,0 +1,683 @@
|
|||||||
|
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||||
|
import { Button } from '@/components/button/button';
|
||||||
|
import { Input } from '@/components/input/input';
|
||||||
|
import { Search, AlertCircle, Check, X, View, Table } from 'lucide-react';
|
||||||
|
import { Checkbox } from '@/components/checkbox/checkbox';
|
||||||
|
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||||
|
import { schemaNameToDomainSchemaName } from '@/lib/domain/db-schema';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogInternalContent,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/dialog/dialog';
|
||||||
|
import type { SelectedTable } from '@/lib/data/import-metadata/filter-metadata';
|
||||||
|
import { generateTableKey } from '@/lib/domain';
|
||||||
|
import { Spinner } from '@/components/spinner/spinner';
|
||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationPrevious,
|
||||||
|
PaginationNext,
|
||||||
|
} from '@/components/pagination/pagination';
|
||||||
|
import { MAX_TABLES_IN_DIAGRAM } from './constants';
|
||||||
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export interface SelectTablesProps {
|
||||||
|
databaseMetadata?: DatabaseMetadata;
|
||||||
|
onImport: ({
|
||||||
|
selectedTables,
|
||||||
|
databaseMetadata,
|
||||||
|
}: {
|
||||||
|
selectedTables?: SelectedTable[];
|
||||||
|
databaseMetadata?: DatabaseMetadata;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onBack: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABLES_PER_PAGE = 10;
|
||||||
|
|
||||||
|
interface TableInfo {
|
||||||
|
key: string;
|
||||||
|
schema?: string;
|
||||||
|
tableName: string;
|
||||||
|
fullName: string;
|
||||||
|
type: 'table' | 'view';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectTables: React.FC<SelectTablesProps> = ({
|
||||||
|
databaseMetadata,
|
||||||
|
onImport,
|
||||||
|
onBack,
|
||||||
|
isLoading = false,
|
||||||
|
}) => {
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [showTables, setShowTables] = useState(true);
|
||||||
|
const [showViews, setShowViews] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isImporting, setIsImporting] = useState(false);
|
||||||
|
|
||||||
|
// Prepare all tables and views with their metadata
|
||||||
|
const allTables = useMemo(() => {
|
||||||
|
const tables: TableInfo[] = [];
|
||||||
|
|
||||||
|
// Add regular tables
|
||||||
|
databaseMetadata?.tables.forEach((table) => {
|
||||||
|
const schema = schemaNameToDomainSchemaName(table.schema);
|
||||||
|
const tableName = table.table;
|
||||||
|
|
||||||
|
const key = `table:${generateTableKey({ tableName, schemaName: schema })}`;
|
||||||
|
|
||||||
|
tables.push({
|
||||||
|
key,
|
||||||
|
schema,
|
||||||
|
tableName,
|
||||||
|
fullName: schema ? `${schema}.${tableName}` : tableName,
|
||||||
|
type: 'table',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add views
|
||||||
|
databaseMetadata?.views?.forEach((view) => {
|
||||||
|
const schema = schemaNameToDomainSchemaName(view.schema);
|
||||||
|
const viewName = view.view_name;
|
||||||
|
|
||||||
|
if (!viewName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = `view:${generateTableKey({
|
||||||
|
tableName: viewName,
|
||||||
|
schemaName: schema,
|
||||||
|
})}`;
|
||||||
|
|
||||||
|
tables.push({
|
||||||
|
key,
|
||||||
|
schema,
|
||||||
|
tableName: viewName,
|
||||||
|
fullName:
|
||||||
|
schema === 'default' ? viewName : `${schema}.${viewName}`,
|
||||||
|
type: 'view',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return tables.sort((a, b) => a.fullName.localeCompare(b.fullName));
|
||||||
|
}, [databaseMetadata?.tables, databaseMetadata?.views]);
|
||||||
|
|
||||||
|
// Count tables and views separately
|
||||||
|
const tableCount = useMemo(
|
||||||
|
() => allTables.filter((t) => t.type === 'table').length,
|
||||||
|
[allTables]
|
||||||
|
);
|
||||||
|
const viewCount = useMemo(
|
||||||
|
() => allTables.filter((t) => t.type === 'view').length,
|
||||||
|
[allTables]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize selectedTables with all tables (not views) if less than 100 tables
|
||||||
|
const [selectedTables, setSelectedTables] = useState<Set<string>>(() => {
|
||||||
|
const tables = allTables.filter((t) => t.type === 'table');
|
||||||
|
if (tables.length < MAX_TABLES_IN_DIAGRAM) {
|
||||||
|
return new Set(tables.map((t) => t.key));
|
||||||
|
}
|
||||||
|
return new Set();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter tables based on search term and type filters
|
||||||
|
const filteredTables = useMemo(() => {
|
||||||
|
let filtered = allTables;
|
||||||
|
|
||||||
|
// Filter by type
|
||||||
|
filtered = filtered.filter((table) => {
|
||||||
|
if (table.type === 'table' && !showTables) return false;
|
||||||
|
if (table.type === 'view' && !showViews) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter by search term
|
||||||
|
if (searchTerm.trim()) {
|
||||||
|
const searchLower = searchTerm.toLowerCase();
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(table) =>
|
||||||
|
table.tableName.toLowerCase().includes(searchLower) ||
|
||||||
|
table.schema?.toLowerCase().includes(searchLower) ||
|
||||||
|
table.fullName.toLowerCase().includes(searchLower)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}, [allTables, searchTerm, showTables, showViews]);
|
||||||
|
|
||||||
|
// Calculate pagination
|
||||||
|
const totalPages = useMemo(
|
||||||
|
() => Math.max(1, Math.ceil(filteredTables.length / TABLES_PER_PAGE)),
|
||||||
|
[filteredTables.length]
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginatedTables = useMemo(() => {
|
||||||
|
const startIndex = (currentPage - 1) * TABLES_PER_PAGE;
|
||||||
|
const endIndex = startIndex + TABLES_PER_PAGE;
|
||||||
|
return filteredTables.slice(startIndex, endIndex);
|
||||||
|
}, [filteredTables, currentPage]);
|
||||||
|
|
||||||
|
// Get currently visible selected tables
|
||||||
|
const visibleSelectedTables = useMemo(() => {
|
||||||
|
return paginatedTables.filter((table) => selectedTables.has(table.key));
|
||||||
|
}, [paginatedTables, selectedTables]);
|
||||||
|
|
||||||
|
const canAddMore = useMemo(
|
||||||
|
() => selectedTables.size < MAX_TABLES_IN_DIAGRAM,
|
||||||
|
[selectedTables.size]
|
||||||
|
);
|
||||||
|
const hasSearchResults = useMemo(
|
||||||
|
() => filteredTables.length > 0,
|
||||||
|
[filteredTables.length]
|
||||||
|
);
|
||||||
|
const allVisibleSelected = useMemo(
|
||||||
|
() =>
|
||||||
|
visibleSelectedTables.length === paginatedTables.length &&
|
||||||
|
paginatedTables.length > 0,
|
||||||
|
[visibleSelectedTables.length, paginatedTables.length]
|
||||||
|
);
|
||||||
|
const canSelectAllFiltered = useMemo(
|
||||||
|
() =>
|
||||||
|
filteredTables.length > 0 &&
|
||||||
|
filteredTables.some((table) => !selectedTables.has(table.key)) &&
|
||||||
|
canAddMore,
|
||||||
|
[filteredTables, selectedTables, canAddMore]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset to first page when search changes
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
const handleTableToggle = useCallback(
|
||||||
|
(tableKey: string) => {
|
||||||
|
const newSelected = new Set(selectedTables);
|
||||||
|
|
||||||
|
if (newSelected.has(tableKey)) {
|
||||||
|
newSelected.delete(tableKey);
|
||||||
|
} else if (selectedTables.size < MAX_TABLES_IN_DIAGRAM) {
|
||||||
|
newSelected.add(tableKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedTables(newSelected);
|
||||||
|
},
|
||||||
|
[selectedTables]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTogglePageSelection = useCallback(() => {
|
||||||
|
const newSelected = new Set(selectedTables);
|
||||||
|
|
||||||
|
if (allVisibleSelected) {
|
||||||
|
// Deselect all on current page
|
||||||
|
for (const table of paginatedTables) {
|
||||||
|
newSelected.delete(table.key);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Select all on current page
|
||||||
|
for (const table of paginatedTables) {
|
||||||
|
if (newSelected.size >= MAX_TABLES_IN_DIAGRAM) break;
|
||||||
|
newSelected.add(table.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedTables(newSelected);
|
||||||
|
}, [allVisibleSelected, paginatedTables, selectedTables]);
|
||||||
|
|
||||||
|
const handleSelectAllFiltered = useCallback(() => {
|
||||||
|
const newSelected = new Set(selectedTables);
|
||||||
|
|
||||||
|
for (const table of filteredTables) {
|
||||||
|
if (newSelected.size >= MAX_TABLES_IN_DIAGRAM) break;
|
||||||
|
newSelected.add(table.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedTables(newSelected);
|
||||||
|
}, [filteredTables, selectedTables]);
|
||||||
|
|
||||||
|
const handleNextPage = useCallback(() => {
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
setCurrentPage(currentPage + 1);
|
||||||
|
}
|
||||||
|
}, [currentPage, totalPages]);
|
||||||
|
|
||||||
|
const handlePrevPage = useCallback(() => {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
setCurrentPage(currentPage - 1);
|
||||||
|
}
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
|
const handleClearSelection = useCallback(() => {
|
||||||
|
setSelectedTables(new Set());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleConfirm = useCallback(async () => {
|
||||||
|
if (isImporting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsImporting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const selectedTableObjects: SelectedTable[] = Array.from(
|
||||||
|
selectedTables
|
||||||
|
)
|
||||||
|
.map((key): SelectedTable | null => {
|
||||||
|
const table = allTables.find((t) => t.key === key);
|
||||||
|
if (!table) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema: table.schema,
|
||||||
|
table: table.tableName,
|
||||||
|
type: table.type,
|
||||||
|
} satisfies SelectedTable;
|
||||||
|
})
|
||||||
|
.filter((t): t is SelectedTable => t !== null);
|
||||||
|
|
||||||
|
await onImport({
|
||||||
|
selectedTables: selectedTableObjects,
|
||||||
|
databaseMetadata,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsImporting(false);
|
||||||
|
}
|
||||||
|
}, [selectedTables, allTables, onImport, databaseMetadata, isImporting]);
|
||||||
|
|
||||||
|
const { isMd: isDesktop } = useBreakpoint('md');
|
||||||
|
|
||||||
|
const renderPagination = useCallback(
|
||||||
|
() => (
|
||||||
|
<Pagination>
|
||||||
|
<PaginationContent>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious
|
||||||
|
onClick={handlePrevPage}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer',
|
||||||
|
currentPage === 1 &&
|
||||||
|
'pointer-events-none opacity-50'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<span className="px-3 text-sm text-muted-foreground">
|
||||||
|
Page {currentPage} of {totalPages}
|
||||||
|
</span>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext
|
||||||
|
onClick={handleNextPage}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer',
|
||||||
|
(currentPage >= totalPages ||
|
||||||
|
filteredTables.length === 0) &&
|
||||||
|
'pointer-events-none opacity-50'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
handlePrevPage,
|
||||||
|
handleNextPage,
|
||||||
|
filteredTables.length,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-[400px] items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<Spinner className="mb-4" />
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Parsing database metadata...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Select Tables to Import</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{tableCount} {tableCount === 1 ? 'table' : 'tables'}
|
||||||
|
{viewCount > 0 && (
|
||||||
|
<>
|
||||||
|
{' and '}
|
||||||
|
{viewCount} {viewCount === 1 ? 'view' : 'views'}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{' found. '}
|
||||||
|
{allTables.length > MAX_TABLES_IN_DIAGRAM
|
||||||
|
? `Select up to ${MAX_TABLES_IN_DIAGRAM} to import.`
|
||||||
|
: 'Choose which ones to import.'}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogInternalContent>
|
||||||
|
<div className="flex h-full flex-col space-y-4">
|
||||||
|
{/* Warning/Info Banner */}
|
||||||
|
{allTables.length > MAX_TABLES_IN_DIAGRAM ? (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2 rounded-lg p-3 text-sm',
|
||||||
|
'bg-amber-50 text-amber-800 dark:bg-amber-950 dark:text-amber-200'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AlertCircle className="size-4 shrink-0" />
|
||||||
|
<span>
|
||||||
|
Due to performance limitations, you can import a
|
||||||
|
maximum of {MAX_TABLES_IN_DIAGRAM} tables.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{/* Search Input */}
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Search tables..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="px-9"
|
||||||
|
/>
|
||||||
|
{searchTerm && (
|
||||||
|
<button
|
||||||
|
onClick={() => setSearchTerm('')}
|
||||||
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
<X className="size-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Selection Status and Actions - Responsive layout */}
|
||||||
|
<div className="flex flex-col items-center gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||||
|
{/* Left side: selection count -> checkboxes -> results found */}
|
||||||
|
<div className="flex flex-col items-center gap-3 text-sm sm:flex-row sm:items-center sm:gap-4">
|
||||||
|
<div className="flex flex-col items-center gap-1 sm:flex-row sm:items-center sm:gap-4">
|
||||||
|
<span className="text-center font-medium">
|
||||||
|
{selectedTables.size} /{' '}
|
||||||
|
{Math.min(
|
||||||
|
MAX_TABLES_IN_DIAGRAM,
|
||||||
|
allTables.length
|
||||||
|
)}{' '}
|
||||||
|
items selected
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 sm:border-x sm:px-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={showTables}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
// Prevent unchecking if it's the only one checked
|
||||||
|
if (!checked && !showViews) return;
|
||||||
|
setShowTables(!!checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
className="size-4"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
<span>tables</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={showViews}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
// Prevent unchecking if it's the only one checked
|
||||||
|
if (!checked && !showTables) return;
|
||||||
|
setShowViews(!!checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
className="size-4"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
<span>views</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="hidden text-muted-foreground sm:inline">
|
||||||
|
{filteredTables.length}{' '}
|
||||||
|
{filteredTables.length === 1
|
||||||
|
? 'result'
|
||||||
|
: 'results'}{' '}
|
||||||
|
found
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right side: action buttons */}
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||||
|
{hasSearchResults && (
|
||||||
|
<>
|
||||||
|
{/* Show page selection button when not searching and no selection */}
|
||||||
|
{!searchTerm &&
|
||||||
|
selectedTables.size === 0 && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={
|
||||||
|
handleTogglePageSelection
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
paginatedTables.length === 0
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{allVisibleSelected
|
||||||
|
? 'Deselect'
|
||||||
|
: 'Select'}{' '}
|
||||||
|
page
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{/* Show Select all button when there are unselected tables */}
|
||||||
|
{canSelectAllFiltered &&
|
||||||
|
selectedTables.size === 0 && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={
|
||||||
|
handleSelectAllFiltered
|
||||||
|
}
|
||||||
|
disabled={!canSelectAllFiltered}
|
||||||
|
title={(() => {
|
||||||
|
const unselectedCount =
|
||||||
|
filteredTables.filter(
|
||||||
|
(table) =>
|
||||||
|
!selectedTables.has(
|
||||||
|
table.key
|
||||||
|
)
|
||||||
|
).length;
|
||||||
|
const remainingCapacity =
|
||||||
|
MAX_TABLES_IN_DIAGRAM -
|
||||||
|
selectedTables.size;
|
||||||
|
if (
|
||||||
|
unselectedCount >
|
||||||
|
remainingCapacity
|
||||||
|
) {
|
||||||
|
return `Can only select ${remainingCapacity} more tables (${MAX_TABLES_IN_DIAGRAM} max limit)`;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})()}
|
||||||
|
>
|
||||||
|
{(() => {
|
||||||
|
const unselectedCount =
|
||||||
|
filteredTables.filter(
|
||||||
|
(table) =>
|
||||||
|
!selectedTables.has(
|
||||||
|
table.key
|
||||||
|
)
|
||||||
|
).length;
|
||||||
|
const remainingCapacity =
|
||||||
|
MAX_TABLES_IN_DIAGRAM -
|
||||||
|
selectedTables.size;
|
||||||
|
if (
|
||||||
|
unselectedCount >
|
||||||
|
remainingCapacity
|
||||||
|
) {
|
||||||
|
return `Select ${remainingCapacity} of ${unselectedCount}`;
|
||||||
|
}
|
||||||
|
return `Select all ${unselectedCount}`;
|
||||||
|
})()}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{selectedTables.size > 0 && (
|
||||||
|
<>
|
||||||
|
{/* Show page selection/deselection button when user has selections */}
|
||||||
|
{paginatedTables.length > 0 && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleTogglePageSelection}
|
||||||
|
>
|
||||||
|
{allVisibleSelected
|
||||||
|
? 'Deselect'
|
||||||
|
: 'Select'}{' '}
|
||||||
|
page
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleClearSelection}
|
||||||
|
>
|
||||||
|
Clear selection
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Table List */}
|
||||||
|
<div className="flex min-h-[428px] flex-1 flex-col">
|
||||||
|
{hasSearchResults ? (
|
||||||
|
<>
|
||||||
|
<div className="flex-1 py-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
{paginatedTables.map((table) => {
|
||||||
|
const isSelected = selectedTables.has(
|
||||||
|
table.key
|
||||||
|
);
|
||||||
|
const isDisabled =
|
||||||
|
!isSelected &&
|
||||||
|
selectedTables.size >=
|
||||||
|
MAX_TABLES_IN_DIAGRAM;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={table.key}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
|
||||||
|
{
|
||||||
|
'cursor-not-allowed':
|
||||||
|
isDisabled,
|
||||||
|
|
||||||
|
'bg-muted hover:bg-muted/80':
|
||||||
|
isSelected,
|
||||||
|
'hover:bg-accent':
|
||||||
|
!isSelected &&
|
||||||
|
!isDisabled,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
disabled={isDisabled}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleTableToggle(
|
||||||
|
table.key
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{table.type === 'view' ? (
|
||||||
|
<View
|
||||||
|
className="size-4"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
className="size-4"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="flex-1">
|
||||||
|
{table.schema ? (
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{table.schema}.
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<span className="font-medium">
|
||||||
|
{table.tableName}
|
||||||
|
</span>
|
||||||
|
{table.type === 'view' && (
|
||||||
|
<span className="ml-2 text-xs text-muted-foreground">
|
||||||
|
(view)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{isSelected && (
|
||||||
|
<Check className="size-4 text-pink-600" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full items-center justify-center py-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{searchTerm
|
||||||
|
? 'No tables found matching your search.'
|
||||||
|
: 'Start typing to search for tables...'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isDesktop ? renderPagination() : null}
|
||||||
|
</DialogInternalContent>
|
||||||
|
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2 md:justify-between md:gap-0">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={onBack}
|
||||||
|
disabled={isImporting}
|
||||||
|
>
|
||||||
|
{t('new_diagram_dialog.back')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleConfirm}
|
||||||
|
disabled={selectedTables.size === 0 || isImporting}
|
||||||
|
className="bg-pink-500 text-white hover:bg-pink-600"
|
||||||
|
>
|
||||||
|
{isImporting ? (
|
||||||
|
<>
|
||||||
|
<Spinner className="mr-2 size-4 text-white" />
|
||||||
|
Importing...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
`Import ${selectedTables.size} Tables`
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{!isDesktop ? renderPagination() : null}
|
||||||
|
</DialogFooter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum CreateDiagramDialogStep {
|
export enum CreateDiagramDialogStep {
|
||||||
SELECT_DATABASE = 'SELECT_DATABASE',
|
SELECT_DATABASE = 'SELECT_DATABASE',
|
||||||
IMPORT_DATABASE = 'IMPORT_DATABASE',
|
IMPORT_DATABASE = 'IMPORT_DATABASE',
|
||||||
|
SELECT_TABLES = 'SELECT_TABLES',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Dialog, DialogContent } from '@/components/dialog/dialog';
|
|||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import { useStorage } from '@/hooks/use-storage';
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useConfig } from '@/hooks/use-config';
|
import { useConfig } from '@/hooks/use-config';
|
||||||
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||||
@@ -15,9 +15,18 @@ import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
|||||||
import { SelectDatabase } from './select-database/select-database';
|
import { SelectDatabase } from './select-database/select-database';
|
||||||
import { CreateDiagramDialogStep } from './create-diagram-dialog-step';
|
import { CreateDiagramDialogStep } from './create-diagram-dialog-step';
|
||||||
import { ImportDatabase } from '../common/import-database/import-database';
|
import { ImportDatabase } from '../common/import-database/import-database';
|
||||||
|
import { SelectTables } from '../common/select-tables/select-tables';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
import { sqlImportToDiagram } from '@/lib/data/sql-import';
|
import { sqlImportToDiagram } from '@/lib/data/sql-import';
|
||||||
|
import type { SelectedTable } from '@/lib/data/import-metadata/filter-metadata';
|
||||||
|
import { filterMetadataByTables } from '@/lib/data/import-metadata/filter-metadata';
|
||||||
|
import { MAX_TABLES_WITHOUT_SHOWING_FILTER } from '../common/select-tables/constants';
|
||||||
|
import {
|
||||||
|
defaultDBMLDiagramName,
|
||||||
|
importDBMLToDiagram,
|
||||||
|
} from '@/lib/dbml/dbml-import/dbml-import';
|
||||||
|
import type { ImportMethod } from '@/lib/import-method/import-method';
|
||||||
|
|
||||||
export interface CreateDiagramDialogProps extends BaseDialogProps {}
|
export interface CreateDiagramDialogProps extends BaseDialogProps {}
|
||||||
|
|
||||||
@@ -26,11 +35,11 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { diagramId } = useChartDB();
|
const { diagramId } = useChartDB();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
|
const [importMethod, setImportMethod] = useState<ImportMethod>('query');
|
||||||
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
||||||
DatabaseType.GENERIC
|
DatabaseType.GENERIC
|
||||||
);
|
);
|
||||||
const { closeCreateDiagramDialog, openImportDBMLDialog } = useDialog();
|
const { closeCreateDiagramDialog } = useDialog();
|
||||||
const { updateConfig } = useConfig();
|
const { updateConfig } = useConfig();
|
||||||
const [scriptResult, setScriptResult] = useState('');
|
const [scriptResult, setScriptResult] = useState('');
|
||||||
const [databaseEdition, setDatabaseEdition] = useState<
|
const [databaseEdition, setDatabaseEdition] = useState<
|
||||||
@@ -42,6 +51,8 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
const { listDiagrams, addDiagram } = useStorage();
|
const { listDiagrams, addDiagram } = useStorage();
|
||||||
const [diagramNumber, setDiagramNumber] = useState<number>(1);
|
const [diagramNumber, setDiagramNumber] = useState<number>(1);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [parsedMetadata, setParsedMetadata] = useState<DatabaseMetadata>();
|
||||||
|
const [isParsingMetadata, setIsParsingMetadata] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDatabaseEdition(undefined);
|
setDatabaseEdition(undefined);
|
||||||
@@ -62,49 +73,80 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
setDatabaseEdition(undefined);
|
setDatabaseEdition(undefined);
|
||||||
setScriptResult('');
|
setScriptResult('');
|
||||||
setImportMethod('query');
|
setImportMethod('query');
|
||||||
|
setParsedMetadata(undefined);
|
||||||
}, [dialog.open]);
|
}, [dialog.open]);
|
||||||
|
|
||||||
const hasExistingDiagram = (diagramId ?? '').trim().length !== 0;
|
const hasExistingDiagram = (diagramId ?? '').trim().length !== 0;
|
||||||
|
|
||||||
const importNewDiagram = useCallback(async () => {
|
const importNewDiagram = useCallback(
|
||||||
let diagram: Diagram | undefined;
|
async ({
|
||||||
|
selectedTables,
|
||||||
|
databaseMetadata,
|
||||||
|
}: {
|
||||||
|
selectedTables?: SelectedTable[];
|
||||||
|
databaseMetadata?: DatabaseMetadata;
|
||||||
|
} = {}) => {
|
||||||
|
let diagram: Diagram | undefined;
|
||||||
|
|
||||||
if (importMethod === 'ddl') {
|
if (importMethod === 'ddl') {
|
||||||
diagram = await sqlImportToDiagram({
|
diagram = await sqlImportToDiagram({
|
||||||
sqlContent: scriptResult,
|
sqlContent: scriptResult,
|
||||||
sourceDatabaseType: databaseType,
|
sourceDatabaseType: databaseType,
|
||||||
targetDatabaseType: databaseType,
|
targetDatabaseType: databaseType,
|
||||||
|
});
|
||||||
|
} else 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 },
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
const databaseMetadata: DatabaseMetadata =
|
|
||||||
loadDatabaseMetadata(scriptResult);
|
|
||||||
|
|
||||||
diagram = await loadFromDatabaseMetadata({
|
closeCreateDiagramDialog();
|
||||||
databaseType,
|
navigate(`/diagrams/${diagram.id}`);
|
||||||
databaseMetadata,
|
},
|
||||||
diagramNumber,
|
[
|
||||||
databaseEdition:
|
importMethod,
|
||||||
databaseEdition?.trim().length === 0
|
databaseType,
|
||||||
? undefined
|
addDiagram,
|
||||||
: databaseEdition,
|
databaseEdition,
|
||||||
});
|
closeCreateDiagramDialog,
|
||||||
}
|
navigate,
|
||||||
|
updateConfig,
|
||||||
await addDiagram({ diagram });
|
scriptResult,
|
||||||
await updateConfig({ config: { defaultDiagramId: diagram.id } });
|
diagramNumber,
|
||||||
closeCreateDiagramDialog();
|
]
|
||||||
navigate(`/diagrams/${diagram.id}`);
|
);
|
||||||
}, [
|
|
||||||
importMethod,
|
|
||||||
databaseType,
|
|
||||||
addDiagram,
|
|
||||||
databaseEdition,
|
|
||||||
closeCreateDiagramDialog,
|
|
||||||
navigate,
|
|
||||||
updateConfig,
|
|
||||||
scriptResult,
|
|
||||||
diagramNumber,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const createEmptyDiagram = useCallback(async () => {
|
const createEmptyDiagram = useCallback(async () => {
|
||||||
const diagram: Diagram = {
|
const diagram: Diagram = {
|
||||||
@@ -123,10 +165,6 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
await updateConfig({ config: { defaultDiagramId: diagram.id } });
|
await updateConfig({ config: { defaultDiagramId: diagram.id } });
|
||||||
closeCreateDiagramDialog();
|
closeCreateDiagramDialog();
|
||||||
navigate(`/diagrams/${diagram.id}`);
|
navigate(`/diagrams/${diagram.id}`);
|
||||||
setTimeout(
|
|
||||||
() => openImportDBMLDialog({ withCreateEmptyDiagram: true }),
|
|
||||||
700
|
|
||||||
);
|
|
||||||
}, [
|
}, [
|
||||||
databaseType,
|
databaseType,
|
||||||
addDiagram,
|
addDiagram,
|
||||||
@@ -135,13 +173,58 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
navigate,
|
navigate,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
diagramNumber,
|
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 (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
{...dialog}
|
{...dialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
// Don't allow closing while parsing metadata
|
||||||
|
if (isParsingMetadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasExistingDiagram) {
|
if (!hasExistingDiagram) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -154,6 +237,8 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex max-h-dvh w-full flex-col md:max-w-[900px]"
|
className="flex max-h-dvh w-full flex-col md:max-w-[900px]"
|
||||||
showClose={hasExistingDiagram}
|
showClose={hasExistingDiagram}
|
||||||
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
|
onEscapeKeyDown={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
||||||
<SelectDatabase
|
<SelectDatabase
|
||||||
@@ -165,9 +250,9 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
|
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : step === CreateDiagramDialogStep.IMPORT_DATABASE ? (
|
||||||
<ImportDatabase
|
<ImportDatabase
|
||||||
onImport={importNewDiagram}
|
onImport={importNewDiagramOrFilterTables}
|
||||||
onCreateEmptyDiagram={createEmptyDiagram}
|
onCreateEmptyDiagram={createEmptyDiagram}
|
||||||
databaseEdition={databaseEdition}
|
databaseEdition={databaseEdition}
|
||||||
databaseType={databaseType}
|
databaseType={databaseType}
|
||||||
@@ -180,8 +265,18 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
title={t('new_diagram_dialog.import_database.title')}
|
title={t('new_diagram_dialog.import_database.title')}
|
||||||
importMethod={importMethod}
|
importMethod={importMethod}
|
||||||
setImportMethod={setImportMethod}
|
setImportMethod={setImportMethod}
|
||||||
|
keepDialogAfterImport={true}
|
||||||
/>
|
/>
|
||||||
)}
|
) : step === CreateDiagramDialogStep.SELECT_TABLES ? (
|
||||||
|
<SelectTables
|
||||||
|
isLoading={isParsingMetadata || !parsedMetadata}
|
||||||
|
databaseMetadata={parsedMetadata}
|
||||||
|
onImport={importNewDiagram}
|
||||||
|
onBack={() =>
|
||||||
|
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { ToggleGroup } from '@/components/toggle/toggle-group';
|
import { ToggleGroup } from '@/components/toggle/toggle-group';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import { DatabaseOption } from './database-option';
|
import { DatabaseOption } from './database-option';
|
||||||
import { ExampleOption } from './example-option';
|
import { ExampleOption } from './example-option';
|
||||||
import { Button } from '@/components/button/button';
|
import { Button } from '@/components/button/button';
|
||||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from '@/components/tabs/tabs';
|
||||||
export interface SelectDatabaseContentProps {
|
export interface SelectDatabaseContentProps {
|
||||||
databaseType: DatabaseType;
|
databaseType: DatabaseType;
|
||||||
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
|
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
|
||||||
onContinue: () => void;
|
onContinue: (selectedDatabaseType: DatabaseType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROW_SIZE = 3;
|
const ROW_SIZE = 3;
|
||||||
const ROWS = 2;
|
const ROWS = 2;
|
||||||
const TOTAL_SLOTS = ROW_SIZE * ROWS;
|
const TOTAL_SLOTS = ROW_SIZE * ROWS;
|
||||||
const SUPPORTED_DB_TYPES: DatabaseType[] = [
|
|
||||||
|
// Transactional databases - OLTP systems optimized for frequent read/write operations
|
||||||
|
const TRANSACTIONAL_DB_TYPES: DatabaseType[] = [
|
||||||
DatabaseType.MYSQL,
|
DatabaseType.MYSQL,
|
||||||
DatabaseType.POSTGRESQL,
|
DatabaseType.POSTGRESQL,
|
||||||
DatabaseType.MARIADB,
|
DatabaseType.MARIADB,
|
||||||
@@ -22,69 +30,87 @@ const SUPPORTED_DB_TYPES: DatabaseType[] = [
|
|||||||
DatabaseType.SQL_SERVER,
|
DatabaseType.SQL_SERVER,
|
||||||
DatabaseType.ORACLE,
|
DatabaseType.ORACLE,
|
||||||
DatabaseType.COCKROACHDB,
|
DatabaseType.COCKROACHDB,
|
||||||
DatabaseType.CLICKHOUSE,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Analytical databases - OLAP systems optimized for complex queries and analytics
|
||||||
|
const ANALYTICAL_DB_TYPES: DatabaseType[] = [DatabaseType.CLICKHOUSE];
|
||||||
|
|
||||||
export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
|
export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
|
||||||
databaseType,
|
databaseType,
|
||||||
setDatabaseType,
|
setDatabaseType,
|
||||||
onContinue,
|
onContinue,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [activeTab, setActiveTab] = useState<'transactional' | 'analytical'>(
|
||||||
|
'transactional'
|
||||||
|
);
|
||||||
const [currentRow, setCurrentRow] = useState(0);
|
const [currentRow, setCurrentRow] = useState(0);
|
||||||
|
|
||||||
|
const currentDbTypes =
|
||||||
|
activeTab === 'transactional'
|
||||||
|
? TRANSACTIONAL_DB_TYPES
|
||||||
|
: ANALYTICAL_DB_TYPES;
|
||||||
|
|
||||||
const currentDatabasesTypes = useMemo(
|
const currentDatabasesTypes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
SUPPORTED_DB_TYPES.slice(
|
currentDbTypes.slice(
|
||||||
currentRow * ROW_SIZE,
|
currentRow * ROW_SIZE,
|
||||||
currentRow * ROW_SIZE + TOTAL_SLOTS
|
currentRow * ROW_SIZE + TOTAL_SLOTS
|
||||||
),
|
),
|
||||||
[currentRow]
|
[currentRow, currentDbTypes]
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasNextRow = useMemo(
|
const hasNextRow = useMemo(
|
||||||
() => (currentRow + 1) * ROW_SIZE < SUPPORTED_DB_TYPES.length,
|
() => (currentRow + 1) * ROW_SIZE < currentDbTypes.length,
|
||||||
[currentRow]
|
[currentRow, currentDbTypes]
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]);
|
const hasPreviousRow = useMemo(() => currentRow > 0, [currentRow]);
|
||||||
|
|
||||||
const toggleRow = () => {
|
const toggleRow = useCallback(() => {
|
||||||
if (currentRow === 0 && hasNextRow) {
|
if (currentRow === 0 && hasNextRow) {
|
||||||
setCurrentRow(currentRow + 1);
|
setCurrentRow(currentRow + 1);
|
||||||
} else if (currentRow > 0) {
|
} else if (currentRow > 0) {
|
||||||
setCurrentRow(currentRow - 1);
|
setCurrentRow(currentRow - 1);
|
||||||
}
|
}
|
||||||
};
|
}, [currentRow, hasNextRow]);
|
||||||
|
|
||||||
return (
|
const handleTabChange = useCallback((value: string) => {
|
||||||
<div className="flex flex-1 flex-col items-center justify-center gap-4">
|
setActiveTab(value as 'transactional' | 'analytical');
|
||||||
<ToggleGroup
|
setCurrentRow(0); // Reset to first row when switching tabs
|
||||||
value={databaseType}
|
}, []);
|
||||||
onValueChange={(value: DatabaseType) => {
|
|
||||||
if (!value) {
|
|
||||||
setDatabaseType(DatabaseType.GENERIC);
|
|
||||||
} else {
|
|
||||||
setDatabaseType(value);
|
|
||||||
onContinue();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type="single"
|
|
||||||
className="grid grid-flow-row grid-cols-3 gap-6"
|
|
||||||
>
|
|
||||||
{Array.from({ length: TOTAL_SLOTS }).map((_, index) =>
|
|
||||||
currentDatabasesTypes?.[index] ? (
|
|
||||||
<DatabaseOption
|
|
||||||
key={currentDatabasesTypes[index]}
|
|
||||||
type={currentDatabasesTypes[index]}
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="col-span-3 flex flex-1 flex-col gap-1">
|
const renderDatabaseGrid = useCallback(
|
||||||
|
() => (
|
||||||
|
<div className="flex min-h-[280px] flex-col md:min-h-[370px]">
|
||||||
|
<ToggleGroup
|
||||||
|
value={databaseType}
|
||||||
|
onValueChange={(value: DatabaseType) => {
|
||||||
|
if (!value) {
|
||||||
|
setDatabaseType(DatabaseType.GENERIC);
|
||||||
|
} else {
|
||||||
|
setDatabaseType(value);
|
||||||
|
onContinue(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="single"
|
||||||
|
className="grid grid-flow-row grid-cols-3 content-start gap-4"
|
||||||
|
>
|
||||||
|
{Array.from({ length: TOTAL_SLOTS }).map((_, index) =>
|
||||||
|
currentDatabasesTypes?.[index] ? (
|
||||||
|
<DatabaseOption
|
||||||
|
key={currentDatabasesTypes[index]}
|
||||||
|
type={currentDatabasesTypes[index]}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
<div className="mt-auto flex flex-col gap-1 pt-4">
|
||||||
{hasNextRow || hasPreviousRow ? (
|
{hasNextRow || hasPreviousRow ? (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={toggleRow}
|
onClick={toggleRow}
|
||||||
className="col-span-3 h-8"
|
className="h-8"
|
||||||
>
|
>
|
||||||
{currentRow === 0 ? (
|
{currentRow === 0 ? (
|
||||||
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 py-3 text-center md:h-12">
|
<div className="flex h-8 w-full cursor-pointer flex-row items-center justify-center gap-2 py-3 text-center md:h-12">
|
||||||
@@ -105,7 +131,55 @@ export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
<ExampleOption />
|
<ExampleOption />
|
||||||
</div>
|
</div>
|
||||||
</ToggleGroup>
|
</div>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
databaseType,
|
||||||
|
currentDatabasesTypes,
|
||||||
|
hasNextRow,
|
||||||
|
hasPreviousRow,
|
||||||
|
onContinue,
|
||||||
|
setDatabaseType,
|
||||||
|
toggleRow,
|
||||||
|
currentRow,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-1 flex-col items-center gap-2">
|
||||||
|
<Tabs
|
||||||
|
defaultValue="transactional"
|
||||||
|
value={activeTab}
|
||||||
|
onValueChange={handleTabChange}
|
||||||
|
className="w-auto"
|
||||||
|
>
|
||||||
|
<TabsList className="mb-2 grid size-auto grid-cols-2 gap-1 rounded-xl border bg-background p-1">
|
||||||
|
<TabsTrigger
|
||||||
|
value="transactional"
|
||||||
|
className="gap-1.5 rounded-lg px-3 py-1 text-sm font-medium transition-all data-[state=active]:bg-sky-600 data-[state=active]:text-white data-[state=inactive]:text-muted-foreground data-[state=active]:shadow-sm data-[state=inactive]:hover:bg-muted/50 data-[state=inactive]:hover:text-foreground dark:data-[state=active]:bg-sky-500"
|
||||||
|
>
|
||||||
|
Transactional
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="analytical"
|
||||||
|
className="gap-1.5 rounded-lg px-3 py-1 text-sm font-medium transition-all data-[state=active]:bg-sky-600 data-[state=active]:text-white data-[state=inactive]:text-muted-foreground data-[state=active]:shadow-sm data-[state=inactive]:hover:bg-muted/50 data-[state=inactive]:hover:text-foreground dark:data-[state=active]:bg-sky-500"
|
||||||
|
>
|
||||||
|
Analytical
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent
|
||||||
|
value="transactional"
|
||||||
|
className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||||
|
>
|
||||||
|
{renderDatabaseGrid()}
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="analytical"
|
||||||
|
className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||||
|
>
|
||||||
|
{renderDatabaseGrid()}
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={createNewDiagram}
|
onClick={createNewDiagram}
|
||||||
|
disabled={databaseType === DatabaseType.GENERIC}
|
||||||
>
|
>
|
||||||
{t('new_diagram_dialog.empty_diagram')}
|
{t('new_diagram_dialog.empty_diagram')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -218,8 +218,14 @@ export const CreateRelationshipDialog: React.FC<
|
|||||||
closeCreateRelationshipDialog();
|
closeCreateRelationshipDialog();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
modal={false}
|
||||||
>
|
>
|
||||||
<DialogContent className="flex flex-col overflow-y-auto" showClose>
|
<DialogContent
|
||||||
|
className="flex flex-col overflow-y-auto"
|
||||||
|
showClose
|
||||||
|
forceOverlay
|
||||||
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{t('create_relationship_dialog.title')}
|
{t('create_relationship_dialog.title')}
|
||||||
|
|||||||
@@ -17,15 +17,21 @@ import { useDialog } from '@/hooks/use-dialog';
|
|||||||
import {
|
import {
|
||||||
exportBaseSQL,
|
exportBaseSQL,
|
||||||
exportSQL,
|
exportSQL,
|
||||||
} from '@/lib/data/export-metadata/export-sql-script';
|
} from '@/lib/data/sql-export/export-sql-script';
|
||||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
|
|
||||||
import { Annoyed, Sparkles } from 'lucide-react';
|
import { Annoyed, Sparkles } from 'lucide-react';
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
|
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||||
|
import {
|
||||||
|
filterDependency,
|
||||||
|
filterRelationship,
|
||||||
|
filterTable,
|
||||||
|
} from '@/lib/domain/diagram-filter/filter';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
|
||||||
export interface ExportSQLDialogProps extends BaseDialogProps {
|
export interface ExportSQLDialogProps extends BaseDialogProps {
|
||||||
targetDatabaseType: DatabaseType;
|
targetDatabaseType: DatabaseType;
|
||||||
@@ -36,7 +42,8 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
targetDatabaseType,
|
targetDatabaseType,
|
||||||
}) => {
|
}) => {
|
||||||
const { closeExportSQLDialog } = useDialog();
|
const { closeExportSQLDialog } = useDialog();
|
||||||
const { currentDiagram, filteredSchemas } = useChartDB();
|
const { currentDiagram } = useChartDB();
|
||||||
|
const { filter } = useDiagramFilter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [script, setScript] = React.useState<string>();
|
const [script, setScript] = React.useState<string>();
|
||||||
const [error, setError] = React.useState<boolean>(false);
|
const [error, setError] = React.useState<boolean>(false);
|
||||||
@@ -48,7 +55,16 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
const filteredDiagram: Diagram = {
|
const filteredDiagram: Diagram = {
|
||||||
...currentDiagram,
|
...currentDiagram,
|
||||||
tables: currentDiagram.tables?.filter((table) =>
|
tables: currentDiagram.tables?.filter((table) =>
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
filterTable({
|
||||||
|
table: {
|
||||||
|
id: table.id,
|
||||||
|
schema: table.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
),
|
),
|
||||||
relationships: currentDiagram.relationships?.filter((rel) => {
|
relationships: currentDiagram.relationships?.filter((rel) => {
|
||||||
const sourceTable = currentDiagram.tables?.find(
|
const sourceTable = currentDiagram.tables?.find(
|
||||||
@@ -60,11 +76,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
sourceTable &&
|
sourceTable &&
|
||||||
targetTable &&
|
targetTable &&
|
||||||
shouldShowTablesBySchemaFilter(
|
filterRelationship({
|
||||||
sourceTable,
|
tableA: {
|
||||||
filteredSchemas
|
id: sourceTable.id,
|
||||||
) &&
|
schema: sourceTable.schema,
|
||||||
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
|
},
|
||||||
|
tableB: {
|
||||||
|
id: targetTable.id,
|
||||||
|
schema: targetTable.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
dependencies: currentDiagram.dependencies?.filter((dep) => {
|
dependencies: currentDiagram.dependencies?.filter((dep) => {
|
||||||
@@ -77,11 +102,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
table &&
|
table &&
|
||||||
dependentTable &&
|
dependentTable &&
|
||||||
shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
|
filterDependency({
|
||||||
shouldShowTablesBySchemaFilter(
|
tableA: {
|
||||||
dependentTable,
|
id: table.id,
|
||||||
filteredSchemas
|
schema: table.schema,
|
||||||
)
|
},
|
||||||
|
tableB: {
|
||||||
|
id: dependentTable.id,
|
||||||
|
schema: dependentTable.schema,
|
||||||
|
},
|
||||||
|
filter,
|
||||||
|
options: {
|
||||||
|
defaultSchema: defaultSchemas[targetDatabaseType],
|
||||||
|
},
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -101,7 +135,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
signal: abortControllerRef.current?.signal,
|
signal: abortControllerRef.current?.signal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [targetDatabaseType, currentDiagram, filteredSchemas]);
|
}, [targetDatabaseType, currentDiagram, filter]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialog.open) {
|
if (!dialog.open) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
|||||||
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||||
import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||||
import type { Diagram } from '@/lib/domain/diagram';
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
|
||||||
import { useChartDB } from '@/hooks/use-chartdb';
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
@@ -15,6 +15,8 @@ import { useReactFlow } from '@xyflow/react';
|
|||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
import { useAlert } from '@/context/alert-context/alert-context';
|
import { useAlert } from '@/context/alert-context/alert-context';
|
||||||
import { sqlImportToDiagram } from '@/lib/data/sql-import';
|
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 {
|
export interface ImportDatabaseDialogProps extends BaseDialogProps {
|
||||||
databaseType: DatabaseType;
|
databaseType: DatabaseType;
|
||||||
@@ -24,7 +26,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
dialog,
|
dialog,
|
||||||
databaseType,
|
databaseType,
|
||||||
}) => {
|
}) => {
|
||||||
const [importMethod, setImportMethod] = useState<'query' | 'ddl'>('query');
|
const [importMethod, setImportMethod] = useState<ImportMethod>('query');
|
||||||
const { closeImportDatabaseDialog } = useDialog();
|
const { closeImportDatabaseDialog } = useDialog();
|
||||||
const { showAlert } = useAlert();
|
const { showAlert } = useAlert();
|
||||||
const {
|
const {
|
||||||
@@ -58,16 +60,17 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
|
|
||||||
const importDatabase = useCallback(async () => {
|
const importDatabase = useCallback(async () => {
|
||||||
let diagram: Diagram | undefined;
|
let diagram: Diagram | undefined;
|
||||||
let warnings: string[] | undefined;
|
|
||||||
|
|
||||||
if (importMethod === 'ddl') {
|
if (importMethod === 'ddl') {
|
||||||
const result = await sqlImportToDiagram({
|
diagram = await sqlImportToDiagram({
|
||||||
sqlContent: scriptResult,
|
sqlContent: scriptResult,
|
||||||
sourceDatabaseType: databaseType,
|
sourceDatabaseType: databaseType,
|
||||||
targetDatabaseType: databaseType,
|
targetDatabaseType: databaseType,
|
||||||
});
|
});
|
||||||
diagram = result;
|
} else if (importMethod === 'dbml') {
|
||||||
warnings = result.warnings;
|
diagram = await importDBMLToDiagram(scriptResult, {
|
||||||
|
databaseType,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const databaseMetadata: DatabaseMetadata =
|
const databaseMetadata: DatabaseMetadata =
|
||||||
loadDatabaseMetadata(scriptResult);
|
loadDatabaseMetadata(scriptResult);
|
||||||
@@ -322,38 +325,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
|||||||
resetRedoStack();
|
resetRedoStack();
|
||||||
resetUndoStack();
|
resetUndoStack();
|
||||||
|
|
||||||
// Show warnings if any
|
closeImportDatabaseDialog();
|
||||||
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,
|
importMethod,
|
||||||
databaseEdition,
|
databaseEdition,
|
||||||
|
|||||||
@@ -1,413 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/dropdown-menu/dropdown-menu';
|
||||||
|
import { Button } from '@/components/button/button';
|
||||||
|
import { Ellipsis, Layers2, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
|
||||||
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import type { Diagram } from '@/lib/domain';
|
||||||
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
|
import { cloneDiagram } from '@/lib/clone';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
interface DiagramRowActionsMenuProps {
|
||||||
|
diagram: Diagram;
|
||||||
|
onOpen: () => void;
|
||||||
|
refetch: () => void;
|
||||||
|
numberOfDiagrams: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DiagramRowActionsMenu: React.FC<DiagramRowActionsMenuProps> = ({
|
||||||
|
diagram,
|
||||||
|
onOpen,
|
||||||
|
refetch,
|
||||||
|
numberOfDiagrams,
|
||||||
|
}) => {
|
||||||
|
const { diagramId } = useChartDB();
|
||||||
|
const { deleteDiagram, addDiagram } = useStorage();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onDelete = useCallback(async () => {
|
||||||
|
deleteDiagram(diagram.id);
|
||||||
|
refetch();
|
||||||
|
|
||||||
|
if (diagram.id === diagramId || numberOfDiagrams <= 1) {
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
}, [deleteDiagram, diagram.id, diagramId, refetch, numberOfDiagrams]);
|
||||||
|
|
||||||
|
const onDuplicate = useCallback(async () => {
|
||||||
|
const duplicatedDiagram = cloneDiagram(diagram);
|
||||||
|
|
||||||
|
const diagramToAdd = duplicatedDiagram.diagram;
|
||||||
|
|
||||||
|
if (!diagramToAdd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
diagramToAdd.name = `${diagram.name} (Copy)`;
|
||||||
|
|
||||||
|
addDiagram({ diagram: diagramToAdd });
|
||||||
|
refetch();
|
||||||
|
}, [addDiagram, refetch, diagram]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="size-8 p-0"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Ellipsis className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={onOpen}
|
||||||
|
className="flex justify-between gap-4"
|
||||||
|
>
|
||||||
|
{t('open_diagram_dialog.diagram_actions.open')}
|
||||||
|
<SquareArrowOutUpRight className="size-3.5" />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={onDuplicate}
|
||||||
|
className="flex justify-between gap-4"
|
||||||
|
>
|
||||||
|
{t('open_diagram_dialog.diagram_actions.duplicate')}
|
||||||
|
<Layers2 className="size-3.5" />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={onDelete}
|
||||||
|
className="flex justify-between gap-4 text-red-700"
|
||||||
|
>
|
||||||
|
{t('open_diagram_dialog.diagram_actions.delete')}
|
||||||
|
<Trash2 className="size-3.5 text-red-700" />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -27,6 +27,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
import { useDebounce } from '@/hooks/use-debounce';
|
import { useDebounce } from '@/hooks/use-debounce';
|
||||||
|
import { DiagramRowActionsMenu } from './diagram-row-actions-menu/diagram-row-actions-menu';
|
||||||
|
|
||||||
export interface OpenDiagramDialogProps extends BaseDialogProps {
|
export interface OpenDiagramDialogProps extends BaseDialogProps {
|
||||||
canClose?: boolean;
|
canClose?: boolean;
|
||||||
@@ -46,21 +47,22 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
|||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchDiagrams = useCallback(async () => {
|
||||||
setSelectedDiagramId(undefined);
|
const diagrams = await listDiagrams({ includeTables: true });
|
||||||
}, [dialog.open]);
|
setDiagrams(
|
||||||
|
diagrams.sort(
|
||||||
|
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [listDiagrams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchDiagrams = async () => {
|
if (!dialog.open) {
|
||||||
const diagrams = await listDiagrams({ includeTables: true });
|
return;
|
||||||
setDiagrams(
|
}
|
||||||
diagrams.sort(
|
setSelectedDiagramId(undefined);
|
||||||
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
fetchDiagrams();
|
fetchDiagrams();
|
||||||
}, [listDiagrams, setDiagrams, dialog.open]);
|
}, [dialog.open, fetchDiagrams]);
|
||||||
|
|
||||||
const openDiagram = useCallback(
|
const openDiagram = useCallback(
|
||||||
(diagramId: string) => {
|
(diagramId: string) => {
|
||||||
@@ -166,6 +168,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
|||||||
'open_diagram_dialog.table_columns.tables_count'
|
'open_diagram_dialog.table_columns.tables_count'
|
||||||
)}
|
)}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@@ -221,6 +224,19 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
|||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{diagram.tables?.length}
|
{diagram.tables?.length}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="items-center p-0 pr-1 text-right">
|
||||||
|
<DiagramRowActionsMenu
|
||||||
|
diagram={diagram}
|
||||||
|
onOpen={() => {
|
||||||
|
openDiagram(diagram.id);
|
||||||
|
closeOpenDiagramDialog();
|
||||||
|
}}
|
||||||
|
numberOfDiagrams={
|
||||||
|
diagrams.length
|
||||||
|
}
|
||||||
|
refetch={fetchDiagrams}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -17,11 +17,23 @@ import type { DBSchema } from '@/lib/domain/db-schema';
|
|||||||
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
import { schemaNameToSchemaId } from '@/lib/domain/db-schema';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Input } from '@/components/input/input';
|
||||||
|
import { Separator } from '@/components/separator/separator';
|
||||||
|
import { Group, SquarePlus } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/tooltip/tooltip';
|
||||||
|
import { useChartDB } from '@/hooks/use-chartdb';
|
||||||
|
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||||
|
import { Label } from '@/components/label/label';
|
||||||
|
|
||||||
export interface TableSchemaDialogProps extends BaseDialogProps {
|
export interface TableSchemaDialogProps extends BaseDialogProps {
|
||||||
table?: DBTable;
|
table?: DBTable;
|
||||||
schemas: DBSchema[];
|
schemas: DBSchema[];
|
||||||
onConfirm: (schema: string) => void;
|
onConfirm: ({ schema }: { schema: DBSchema }) => void;
|
||||||
|
allowSchemaCreation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
||||||
@@ -29,27 +41,73 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
|||||||
table,
|
table,
|
||||||
schemas,
|
schemas,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
|
allowSchemaCreation = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedSchema, setSelectedSchema] = React.useState<string>(
|
const { databaseType } = useChartDB();
|
||||||
|
const [selectedSchemaId, setSelectedSchemaId] = useState<string>(
|
||||||
table?.schema
|
table?.schema
|
||||||
? schemaNameToSchemaId(table.schema)
|
? schemaNameToSchemaId(table.schema)
|
||||||
: (schemas?.[0]?.id ?? '')
|
: (schemas?.[0]?.id ?? '')
|
||||||
);
|
);
|
||||||
|
const allowSchemaSelection = useMemo(
|
||||||
|
() => schemas && schemas.length > 0,
|
||||||
|
[schemas]
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultSchemaName = useMemo(
|
||||||
|
() => defaultSchemas?.[databaseType],
|
||||||
|
[databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [isCreatingNew, setIsCreatingNew] =
|
||||||
|
useState<boolean>(!allowSchemaSelection);
|
||||||
|
const [newSchemaName, setNewSchemaName] = useState<string>(
|
||||||
|
allowSchemaCreation && !allowSchemaSelection
|
||||||
|
? (defaultSchemaName ?? '')
|
||||||
|
: ''
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialog.open) return;
|
if (!dialog.open) return;
|
||||||
setSelectedSchema(
|
setSelectedSchemaId(
|
||||||
table?.schema
|
table?.schema
|
||||||
? schemaNameToSchemaId(table.schema)
|
? schemaNameToSchemaId(table.schema)
|
||||||
: (schemas?.[0]?.id ?? '')
|
: (schemas?.[0]?.id ?? '')
|
||||||
);
|
);
|
||||||
}, [dialog.open, schemas, table?.schema]);
|
setIsCreatingNew(!allowSchemaSelection);
|
||||||
|
setNewSchemaName(
|
||||||
|
allowSchemaCreation && !allowSchemaSelection
|
||||||
|
? (defaultSchemaName ?? '')
|
||||||
|
: ''
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
defaultSchemaName,
|
||||||
|
dialog.open,
|
||||||
|
schemas,
|
||||||
|
table?.schema,
|
||||||
|
allowSchemaSelection,
|
||||||
|
allowSchemaCreation,
|
||||||
|
]);
|
||||||
|
|
||||||
const { closeTableSchemaDialog } = useDialog();
|
const { closeTableSchemaDialog } = useDialog();
|
||||||
|
|
||||||
const handleConfirm = useCallback(() => {
|
const handleConfirm = useCallback(() => {
|
||||||
onConfirm(selectedSchema);
|
if (isCreatingNew && newSchemaName.trim()) {
|
||||||
}, [onConfirm, selectedSchema]);
|
const newSchema: DBSchema = {
|
||||||
|
id: schemaNameToSchemaId(newSchemaName.trim()),
|
||||||
|
name: newSchemaName.trim(),
|
||||||
|
tableCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
onConfirm({ schema: newSchema });
|
||||||
|
} else {
|
||||||
|
const schema = schemas.find((s) => s.id === selectedSchemaId);
|
||||||
|
if (!schema) return;
|
||||||
|
|
||||||
|
onConfirm({ schema });
|
||||||
|
}
|
||||||
|
}, [onConfirm, selectedSchemaId, schemas, isCreatingNew, newSchemaName]);
|
||||||
|
|
||||||
const schemaOptions: SelectBoxOption[] = useMemo(
|
const schemaOptions: SelectBoxOption[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -60,6 +118,25 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
|||||||
[schemas]
|
[schemas]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderSwitchCreateOrSelectButton = useCallback(
|
||||||
|
() => (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start"
|
||||||
|
onClick={() => setIsCreatingNew(!isCreatingNew)}
|
||||||
|
disabled={!allowSchemaSelection || !allowSchemaCreation}
|
||||||
|
>
|
||||||
|
{!isCreatingNew ? (
|
||||||
|
<SquarePlus className="mr-2 size-4 " />
|
||||||
|
) : (
|
||||||
|
<Group className="mr-2 size-4 " />
|
||||||
|
)}
|
||||||
|
{isCreatingNew ? 'Select existing schema' : 'Create new schema'}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
[isCreatingNew, allowSchemaSelection, allowSchemaCreation]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
{...dialog}
|
{...dialog}
|
||||||
@@ -67,48 +144,106 @@ export const TableSchemaDialog: React.FC<TableSchemaDialogProps> = ({
|
|||||||
if (!open) {
|
if (!open) {
|
||||||
closeTableSchemaDialog();
|
closeTableSchemaDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => (document.body.style.pointerEvents = ''), 500);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent className="flex flex-col" showClose>
|
<DialogContent className="flex flex-col" showClose>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{table
|
{!allowSchemaSelection && allowSchemaCreation
|
||||||
? t('update_table_schema_dialog.title')
|
? t('create_table_schema_dialog.title')
|
||||||
: t('new_table_schema_dialog.title')}
|
: table
|
||||||
|
? t('update_table_schema_dialog.title')
|
||||||
|
: t('new_table_schema_dialog.title')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{table
|
{!allowSchemaSelection && allowSchemaCreation
|
||||||
? t('update_table_schema_dialog.description', {
|
? t('create_table_schema_dialog.description')
|
||||||
tableName: table.name,
|
: table
|
||||||
})
|
? t('update_table_schema_dialog.description', {
|
||||||
: t('new_table_schema_dialog.description')}
|
tableName: table.name,
|
||||||
|
})
|
||||||
|
: t('new_table_schema_dialog.description')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-1">
|
<div className="grid gap-4 py-1">
|
||||||
<div className="grid w-full items-center gap-4">
|
<div className="grid w-full items-center gap-4">
|
||||||
<SelectBox
|
{!isCreatingNew ? (
|
||||||
options={schemaOptions}
|
<SelectBox
|
||||||
multiple={false}
|
options={schemaOptions}
|
||||||
value={selectedSchema}
|
multiple={false}
|
||||||
onChange={(value) =>
|
value={selectedSchemaId}
|
||||||
setSelectedSchema(value as string)
|
onChange={(value) =>
|
||||||
}
|
setSelectedSchemaId(value as string)
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{allowSchemaCreation &&
|
||||||
|
!allowSchemaSelection ? (
|
||||||
|
<Label htmlFor="new-schema-name">
|
||||||
|
Schema Name
|
||||||
|
</Label>
|
||||||
|
) : null}
|
||||||
|
<Input
|
||||||
|
id="new-schema-name"
|
||||||
|
value={newSchemaName}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewSchemaName(e.target.value)
|
||||||
|
}
|
||||||
|
placeholder={`Enter schema name.${defaultSchemaName ? ` e.g. ${defaultSchemaName}.` : ''}`}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{allowSchemaCreation && allowSchemaSelection ? (
|
||||||
|
<>
|
||||||
|
<div className="relative">
|
||||||
|
<Separator className="my-2" />
|
||||||
|
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-2 text-xs text-muted-foreground">
|
||||||
|
or
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{allowSchemaSelection ? (
|
||||||
|
renderSwitchCreateOrSelectButton()
|
||||||
|
) : (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>
|
||||||
|
{renderSwitchCreateOrSelectButton()}
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>No existing schemas available</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="flex gap-1 md:justify-between">
|
<DialogFooter className="flex gap-1 md:justify-between">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
{table
|
{isCreatingNew
|
||||||
? t('update_table_schema_dialog.cancel')
|
? t('create_table_schema_dialog.cancel')
|
||||||
: t('new_table_schema_dialog.cancel')}
|
: table
|
||||||
|
? t('update_table_schema_dialog.cancel')
|
||||||
|
: t('new_table_schema_dialog.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button onClick={handleConfirm}>
|
<Button
|
||||||
{table
|
onClick={handleConfirm}
|
||||||
? t('update_table_schema_dialog.confirm')
|
disabled={isCreatingNew && !newSchemaName.trim()}
|
||||||
: t('new_table_schema_dialog.confirm')}
|
>
|
||||||
|
{isCreatingNew
|
||||||
|
? t('create_table_schema_dialog.create')
|
||||||
|
: table
|
||||||
|
? t('update_table_schema_dialog.confirm')
|
||||||
|
: t('new_table_schema_dialog.confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
|
overscroll-behavior-x: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-editable {
|
.text-editable {
|
||||||
@@ -154,3 +155,29 @@
|
|||||||
background-size: 650%;
|
background-size: 650%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Edit button emphasis animation */
|
||||||
|
@keyframes dbml_edit-button-emphasis {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
|
||||||
|
background-color: rgba(59, 130, 246, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
|
||||||
|
background-color: rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
||||||
|
background-color: rgba(59, 130, 246, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dbml-edit-button-emphasis {
|
||||||
|
animation: dbml_edit-button-emphasis 0.6s ease-in-out;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|||||||
181
src/hooks/use-focus-on.ts
Normal file
181
src/hooks/use-focus-on.ts
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
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 focusOnNote = useCallback(
|
||||||
|
(noteId: string, options: FocusOptions = {}) => {
|
||||||
|
const { select = true } = options;
|
||||||
|
|
||||||
|
if (select) {
|
||||||
|
setNodes((nodes) =>
|
||||||
|
nodes.map((node) =>
|
||||||
|
node.id === noteId
|
||||||
|
? {
|
||||||
|
...node,
|
||||||
|
selected: true,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...node,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fitView({
|
||||||
|
duration: 500,
|
||||||
|
maxZoom: 1,
|
||||||
|
minZoom: 1,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: noteId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
focusOnNote,
|
||||||
|
focusOnRelationship,
|
||||||
|
};
|
||||||
|
};
|
||||||
379
src/hooks/use-update-table-field.ts
Normal file
379
src/hooks/use-update-table-field.ts
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
||||||
|
import { useChartDB } from './use-chartdb';
|
||||||
|
import { useDebounce } from './use-debounce-v2';
|
||||||
|
import type { DatabaseType, DBField, DBTable } from '@/lib/domain';
|
||||||
|
import type {
|
||||||
|
SelectBoxOption,
|
||||||
|
SelectBoxProps,
|
||||||
|
} from '@/components/select-box/select-box';
|
||||||
|
import {
|
||||||
|
dataTypeDataToDataType,
|
||||||
|
sortedDataTypeMap,
|
||||||
|
supportsArrayDataType,
|
||||||
|
autoIncrementAlwaysOn,
|
||||||
|
requiresNotNull,
|
||||||
|
} 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,
|
||||||
|
databaseType: DatabaseType
|
||||||
|
): {
|
||||||
|
regex?: string;
|
||||||
|
extractRegex?: RegExp;
|
||||||
|
} => {
|
||||||
|
const typeName = dataType.name;
|
||||||
|
const supportsArrays = supportsArrayDataType(dataType.id, databaseType);
|
||||||
|
const arrayPattern = supportsArrays ? '(\\[\\])?' : '';
|
||||||
|
|
||||||
|
if (!dataType.fieldAttributes) {
|
||||||
|
// For types without field attributes, support plain type + optional array notation
|
||||||
|
return {
|
||||||
|
regex: `^${typeName}${arrayPattern}$`,
|
||||||
|
extractRegex: new RegExp(`^${typeName}${arrayPattern}$`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldAttributes = dataType.fieldAttributes;
|
||||||
|
|
||||||
|
if (fieldAttributes.hasCharMaxLength) {
|
||||||
|
if (fieldAttributes.hasCharMaxLengthOption) {
|
||||||
|
return {
|
||||||
|
regex: `^${typeName}\\((\\d+|[mM][aA][xX])\\)${arrayPattern}$`,
|
||||||
|
extractRegex: supportsArrays
|
||||||
|
? /\((\d+|max)\)(\[\])?/i
|
||||||
|
: /\((\d+|max)\)/i,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
regex: `^${typeName}\\(\\d+\\)${arrayPattern}$`,
|
||||||
|
extractRegex: supportsArrays ? /\((\d+)\)(\[\])?/ : /\((\d+)\)/,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldAttributes.precision && fieldAttributes.scale) {
|
||||||
|
return {
|
||||||
|
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*(?:,\\s*\\d+\\s*)?\\)${arrayPattern}$`,
|
||||||
|
extractRegex: new RegExp(
|
||||||
|
`${typeName}\\s*\\(\\s*(\\d+)\\s*(?:,\\s*(\\d+)\\s*)?\\)${arrayPattern}`
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldAttributes.precision) {
|
||||||
|
return {
|
||||||
|
regex: `^${typeName}\\s*\\(\\s*\\d+\\s*\\)${arrayPattern}$`,
|
||||||
|
extractRegex: supportsArrays ? /\((\d+)\)(\[\])?/ : /\((\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);
|
||||||
|
|
||||||
|
const lastFieldNameRef = useRef<string>(field.name);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (localFieldName === lastFieldNameRef.current) {
|
||||||
|
lastFieldNameRef.current = field.name;
|
||||||
|
setLocalFieldName(field.name);
|
||||||
|
}
|
||||||
|
}, [field.name, localFieldName]);
|
||||||
|
|
||||||
|
// Update local state when field properties change externally
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalNullable(field.nullable);
|
||||||
|
setLocalPrimaryKey(field.primaryKey);
|
||||||
|
}, [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,
|
||||||
|
databaseType
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
let isArray: boolean | undefined = undefined;
|
||||||
|
|
||||||
|
if (regexMatches?.length) {
|
||||||
|
// Check if the last captured group is the array indicator []
|
||||||
|
const lastMatch = regexMatches[regexMatches.length - 1];
|
||||||
|
const hasArrayIndicator = lastMatch === '[]';
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set isArray if the array indicator was found and the type supports arrays
|
||||||
|
if (hasArrayIndicator) {
|
||||||
|
const typeId = value as string;
|
||||||
|
if (supportsArrayDataType(typeId, databaseType)) {
|
||||||
|
isArray = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Explicitly set to false/undefined if no array indicator
|
||||||
|
isArray = undefined;
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTypeName = dataType?.name ?? (value as string);
|
||||||
|
const typeRequiresNotNull = requiresNotNull(newTypeName);
|
||||||
|
const shouldForceIncrement = autoIncrementAlwaysOn(newTypeName);
|
||||||
|
|
||||||
|
updateField(table.id, field.id, {
|
||||||
|
characterMaximumLength,
|
||||||
|
precision,
|
||||||
|
scale,
|
||||||
|
isArray,
|
||||||
|
...(typeRequiresNotNull ? { nullable: false } : {}),
|
||||||
|
increment: shouldForceIncrement ? true : 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) => {
|
||||||
|
const updates: Partial<DBField> = { nullable: value };
|
||||||
|
|
||||||
|
// If setting to nullable, clear increment (auto-increment requires NOT NULL)
|
||||||
|
if (value && field.increment) {
|
||||||
|
updates.increment = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateField(table.id, field.id, updates);
|
||||||
|
},
|
||||||
|
[updateField, table.id, field.id, field.increment]
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
isArray: field.isArray && typeId === field.type.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
42
src/hooks/use-update-table.ts
Normal file
42
src/hooks/use-update-table.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { useCallback, useState, useEffect } from 'react';
|
||||||
|
import { useChartDB } from './use-chartdb';
|
||||||
|
import { useDebounce } from './use-debounce-v2';
|
||||||
|
import type { DBTable } from '@/lib/domain';
|
||||||
|
|
||||||
|
// Hook for updating table properties with debouncing for performance
|
||||||
|
export const useUpdateTable = (table: DBTable) => {
|
||||||
|
const { updateTable: chartDBUpdateTable } = useChartDB();
|
||||||
|
const [localTableName, setLocalTableName] = useState(table.name);
|
||||||
|
|
||||||
|
// Debounced update function
|
||||||
|
const debouncedUpdate = useDebounce(
|
||||||
|
useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (value.trim() && value.trim() !== table.name) {
|
||||||
|
chartDBUpdateTable(table.id, { name: value.trim() });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[chartDBUpdateTable, table.id, table.name]
|
||||||
|
),
|
||||||
|
1000 // 1000ms debounce
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update local state immediately for responsive UI
|
||||||
|
const handleTableNameChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setLocalTableName(value);
|
||||||
|
debouncedUpdate(value);
|
||||||
|
},
|
||||||
|
[debouncedUpdate]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update local state when table name changes externally
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalTableName(table.name);
|
||||||
|
}, [table.name]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
tableName: localTableName,
|
||||||
|
handleTableNameChange,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -23,23 +23,25 @@ import { bn, bnMetadata } from './locales/bn';
|
|||||||
import { gu, guMetadata } from './locales/gu';
|
import { gu, guMetadata } from './locales/gu';
|
||||||
import { vi, viMetadata } from './locales/vi';
|
import { vi, viMetadata } from './locales/vi';
|
||||||
import { ar, arMetadata } from './locales/ar';
|
import { ar, arMetadata } from './locales/ar';
|
||||||
|
import { hr, hrMetadata } from './locales/hr';
|
||||||
|
|
||||||
export const languages: LanguageMetadata[] = [
|
export const languages: LanguageMetadata[] = [
|
||||||
enMetadata,
|
enMetadata,
|
||||||
esMetadata,
|
|
||||||
frMetadata,
|
frMetadata,
|
||||||
deMetadata,
|
deMetadata,
|
||||||
|
esMetadata,
|
||||||
|
ukMetadata,
|
||||||
|
ruMetadata,
|
||||||
|
trMetadata,
|
||||||
|
hrMetadata,
|
||||||
|
pt_BRMetadata,
|
||||||
hiMetadata,
|
hiMetadata,
|
||||||
jaMetadata,
|
jaMetadata,
|
||||||
ko_KRMetadata,
|
ko_KRMetadata,
|
||||||
pt_BRMetadata,
|
|
||||||
ukMetadata,
|
|
||||||
ruMetadata,
|
|
||||||
zh_CNMetadata,
|
zh_CNMetadata,
|
||||||
zh_TWMetadata,
|
zh_TWMetadata,
|
||||||
neMetadata,
|
neMetadata,
|
||||||
mrMetadata,
|
mrMetadata,
|
||||||
trMetadata,
|
|
||||||
id_IDMetadata,
|
id_IDMetadata,
|
||||||
teMetadata,
|
teMetadata,
|
||||||
bnMetadata,
|
bnMetadata,
|
||||||
@@ -70,6 +72,7 @@ const resources = {
|
|||||||
gu,
|
gu,
|
||||||
vi,
|
vi,
|
||||||
ar,
|
ar,
|
||||||
|
hr,
|
||||||
};
|
};
|
||||||
|
|
||||||
i18n.use(LanguageDetector)
|
i18n.use(LanguageDetector)
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const ar: LanguageTranslation = {
|
export const ar: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'جديد',
|
||||||
|
browse: 'تصفح',
|
||||||
|
tables: 'الجداول',
|
||||||
|
refs: 'المراجع',
|
||||||
|
dependencies: 'التبعيات',
|
||||||
|
custom_types: 'الأنواع المخصصة',
|
||||||
|
visuals: 'مرئيات',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'ملف',
|
actions: 'الإجراءات',
|
||||||
new: 'جديد',
|
new: 'جديد...',
|
||||||
open: 'فتح',
|
browse: 'تصفح...',
|
||||||
save: 'حفظ',
|
save: 'حفظ',
|
||||||
import: 'استيراد قاعدة بيانات',
|
import: 'استيراد قاعدة بيانات',
|
||||||
export_sql: 'SQL تصدير',
|
export_sql: 'SQL تصدير',
|
||||||
export_as: 'تصدير كـ',
|
export_as: 'تصدير كـ',
|
||||||
delete_diagram: 'حذف الرسم البياني',
|
delete_diagram: 'حذف',
|
||||||
exit: 'خروج',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'تحرير',
|
edit: 'تحرير',
|
||||||
@@ -26,7 +34,10 @@ export const ar: LanguageTranslation = {
|
|||||||
hide_sidebar: 'إخفاء الشريط الجانبي',
|
hide_sidebar: 'إخفاء الشريط الجانبي',
|
||||||
hide_cardinality: 'إخفاء الكاردينالية',
|
hide_cardinality: 'إخفاء الكاردينالية',
|
||||||
show_cardinality: 'إظهار الكاردينالية',
|
show_cardinality: 'إظهار الكاردينالية',
|
||||||
|
hide_field_attributes: 'إخفاء خصائص الحقل',
|
||||||
|
show_field_attributes: 'إظهار خصائص الحقل',
|
||||||
zoom_on_scroll: 'تكبير/تصغير عند التمرير',
|
zoom_on_scroll: 'تكبير/تصغير عند التمرير',
|
||||||
|
show_views: 'عروض قاعدة البيانات',
|
||||||
theme: 'المظهر',
|
theme: 'المظهر',
|
||||||
show_dependencies: 'إظهار الاعتمادات',
|
show_dependencies: 'إظهار الاعتمادات',
|
||||||
hide_dependencies: 'إخفاء الاعتمادات',
|
hide_dependencies: 'إخفاء الاعتمادات',
|
||||||
@@ -63,22 +74,13 @@ export const ar: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'إعادة ترتيب الرسم البياني',
|
title: 'ترتيب تلقائي للرسم البياني',
|
||||||
description:
|
description:
|
||||||
'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
|
'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
|
||||||
reorder: 'إعادة ترتيب',
|
reorder: 'ترتيب تلقائي',
|
||||||
cancel: 'إلغاء',
|
cancel: 'إلغاء',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'مخططات متعددة',
|
|
||||||
description:
|
|
||||||
'{{formattedSchemas}} :مخططات في هذا الرسم البياني. يتم حاليا عرض {{schemasCount}} هناك',
|
|
||||||
dont_show_again: 'لا تظهره مجدداً',
|
|
||||||
change_schema: 'تغيير',
|
|
||||||
none: 'لا شيء',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'فشل النسخ',
|
title: 'فشل النسخ',
|
||||||
@@ -113,14 +115,11 @@ export const ar: LanguageTranslation = {
|
|||||||
copied: '!تم النسخ',
|
copied: '!تم النسخ',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: ':المخطط',
|
|
||||||
filter_by_schema: 'تصفية حسب المخطط',
|
|
||||||
search_schema: '...بحث في المخطط',
|
|
||||||
no_schemas_found: '.لم يتم العثور على مخططات',
|
|
||||||
view_all_options: '...عرض جميع الخيارات',
|
view_all_options: '...عرض جميع الخيارات',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'الجداول',
|
tables: 'الجداول',
|
||||||
add_table: 'إضافة جدول',
|
add_table: 'إضافة جدول',
|
||||||
|
add_view: 'إضافة عرض',
|
||||||
filter: 'تصفية',
|
filter: 'تصفية',
|
||||||
collapse: 'طي الكل',
|
collapse: 'طي الكل',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -146,16 +145,22 @@ export const ar: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'خصائص الحقل',
|
title: 'خصائص الحقل',
|
||||||
unique: 'فريد',
|
unique: 'فريد',
|
||||||
|
auto_increment: 'زيادة تلقائية',
|
||||||
comments: 'تعليقات',
|
comments: 'تعليقات',
|
||||||
no_comments: 'لا يوجد تعليقات',
|
no_comments: 'لا يوجد تعليقات',
|
||||||
delete_field: 'حذف الحقل',
|
delete_field: 'حذف الحقل',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'الدقة',
|
||||||
|
scale: 'النطاق',
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'خصائص الفهرس',
|
title: 'خصائص الفهرس',
|
||||||
name: 'الإسم',
|
name: 'الإسم',
|
||||||
unique: 'فريد',
|
unique: 'فريد',
|
||||||
|
index_type: 'نوع الفهرس',
|
||||||
delete_index: 'حذف الفهرس',
|
delete_index: 'حذف الفهرس',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -172,12 +177,15 @@ export const ar: LanguageTranslation = {
|
|||||||
description: 'أنشئ جدولاً للبدء',
|
description: 'أنشئ جدولاً للبدء',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'العلاقات',
|
refs: 'المراجع',
|
||||||
filter: 'تصفية',
|
filter: 'تصفية',
|
||||||
add_relationship: 'إضافة علاقة',
|
|
||||||
collapse: 'طي الكل',
|
collapse: 'طي الكل',
|
||||||
|
add_relationship: 'إضافة علاقة',
|
||||||
|
relationships: 'العلاقات',
|
||||||
|
dependencies: 'الاعتمادات',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'العلاقة',
|
||||||
primary: 'الجدول الأساسي',
|
primary: 'الجدول الأساسي',
|
||||||
foreign: 'الجدول المرتبط',
|
foreign: 'الجدول المرتبط',
|
||||||
cardinality: 'الكاردينالية',
|
cardinality: 'الكاردينالية',
|
||||||
@@ -187,16 +195,8 @@ export const ar: LanguageTranslation = {
|
|||||||
delete_relationship: 'حذف',
|
delete_relationship: 'حذف',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'لا توجد علاقات',
|
|
||||||
description: 'إنشئ علاقة لربط الجداول',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'الاعتمادات',
|
|
||||||
filter: 'تصفية',
|
|
||||||
collapse: 'طي الكل',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'الاعتماد',
|
||||||
table: 'الجدول',
|
table: 'الجدول',
|
||||||
dependent_table: 'عرض الاعتمادات',
|
dependent_table: 'عرض الاعتمادات',
|
||||||
delete_dependency: 'حذف',
|
delete_dependency: 'حذف',
|
||||||
@@ -206,8 +206,8 @@ export const ar: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'لا توجد اعتمادات',
|
title: 'لا توجد علاقات',
|
||||||
description: 'إنشاء اعتماد للبدء',
|
description: 'إنشاء علاقة للبدء',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -232,6 +232,33 @@ export const ar: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'مرئيات',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'ملاحظات',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'تصفية',
|
||||||
|
add_note: 'إضافة ملاحظة',
|
||||||
|
no_results: 'لم يتم العثور على ملاحظات',
|
||||||
|
clear: 'مسح التصفية',
|
||||||
|
empty_state: {
|
||||||
|
title: 'لا توجد ملاحظات',
|
||||||
|
description: 'أنشئ ملاحظة لإضافة تعليقات نصية على اللوحة',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'ملاحظة فارغة',
|
||||||
|
note_actions: {
|
||||||
|
title: 'إجراءات الملاحظة',
|
||||||
|
edit_content: 'تحرير المحتوى',
|
||||||
|
delete_note: 'حذف الملاحظة',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -248,12 +275,16 @@ export const ar: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'لم يتم تحديد قيم التعداد',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -267,8 +298,13 @@ export const ar: LanguageTranslation = {
|
|||||||
show_all: 'عرض الكل',
|
show_all: 'عرض الكل',
|
||||||
undo: 'تراجع',
|
undo: 'تراجع',
|
||||||
redo: 'إعادة',
|
redo: 'إعادة',
|
||||||
reorder_diagram: 'إعادة ترتيب الرسم البياني',
|
reorder_diagram: 'ترتيب تلقائي للرسم البياني',
|
||||||
highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
|
highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -299,13 +335,13 @@ export const ar: LanguageTranslation = {
|
|||||||
cancel: 'إلغاء',
|
cancel: 'إلغاء',
|
||||||
import_from_file: 'استيراد من ملف',
|
import_from_file: 'استيراد من ملف',
|
||||||
back: 'رجوع',
|
back: 'رجوع',
|
||||||
empty_diagram: 'مخطط فارغ',
|
empty_diagram: 'قاعدة بيانات فارغة',
|
||||||
continue: 'متابعة',
|
continue: 'متابعة',
|
||||||
import: 'استيراد',
|
import: 'استيراد',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'فتح مخطط',
|
title: 'فتح قاعدة بيانات',
|
||||||
description: 'اختر مخططًا لفتحه من القائمة ادناه',
|
description: 'اختر مخططًا لفتحه من القائمة ادناه',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'الإسم',
|
name: 'الإسم',
|
||||||
@@ -315,6 +351,12 @@ export const ar: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'إلغاء',
|
cancel: 'إلغاء',
|
||||||
open: 'فتح',
|
open: 'فتح',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'فتح',
|
||||||
|
duplicate: 'تكرار',
|
||||||
|
delete: 'حذف',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -400,6 +442,13 @@ export const ar: LanguageTranslation = {
|
|||||||
cancel: 'إلغاء',
|
cancel: 'إلغاء',
|
||||||
confirm: 'تغيير',
|
confirm: 'تغيير',
|
||||||
},
|
},
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'إنشاء مخطط جديد',
|
||||||
|
description:
|
||||||
|
'لا توجد مخططات حتى الآن. قم بإنشاء أول مخطط لتنظيم جداولك.',
|
||||||
|
create: 'إنشاء',
|
||||||
|
cancel: 'إلغاء',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: '!ساعدنا على التحسن',
|
title: '!ساعدنا على التحسن',
|
||||||
@@ -453,9 +502,11 @@ export const ar: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'جدول جديد',
|
new_table: 'جدول جديد',
|
||||||
|
new_view: 'عرض جديد',
|
||||||
new_relationship: 'علاقة جديدة',
|
new_relationship: 'علاقة جديدة',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'ملاحظة جديدة',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -474,6 +525,8 @@ export const ar: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'اللغة',
|
change_language: 'اللغة',
|
||||||
},
|
},
|
||||||
|
on: 'تشغيل',
|
||||||
|
off: 'إيقاف',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const bn: LanguageTranslation = {
|
export const bn: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'নতুন',
|
||||||
|
browse: 'ব্রাউজ',
|
||||||
|
tables: 'টেবিল',
|
||||||
|
refs: 'রেফস',
|
||||||
|
dependencies: 'নির্ভরতা',
|
||||||
|
custom_types: 'কাস্টম টাইপ',
|
||||||
|
visuals: 'ভিজ্যুয়াল',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'ফাইল',
|
actions: 'কার্য',
|
||||||
new: 'নতুন',
|
new: 'নতুন...',
|
||||||
open: 'খুলুন',
|
browse: 'ব্রাউজ করুন...',
|
||||||
save: 'সংরক্ষণ করুন',
|
save: 'সংরক্ষণ করুন',
|
||||||
import: 'ডাটাবেস আমদানি করুন',
|
import: 'ডাটাবেস আমদানি করুন',
|
||||||
export_sql: 'SQL রপ্তানি করুন',
|
export_sql: 'SQL রপ্তানি করুন',
|
||||||
export_as: 'রূপে রপ্তানি করুন',
|
export_as: 'রূপে রপ্তানি করুন',
|
||||||
delete_diagram: 'ডায়াগ্রাম মুছুন',
|
delete_diagram: 'মুছুন',
|
||||||
exit: 'প্রস্থান করুন',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'সম্পাদনা',
|
edit: 'সম্পাদনা',
|
||||||
@@ -26,7 +34,10 @@ export const bn: LanguageTranslation = {
|
|||||||
hide_sidebar: 'সাইডবার লুকান',
|
hide_sidebar: 'সাইডবার লুকান',
|
||||||
hide_cardinality: 'কার্ডিনালিটি লুকান',
|
hide_cardinality: 'কার্ডিনালিটি লুকান',
|
||||||
show_cardinality: 'কার্ডিনালিটি দেখান',
|
show_cardinality: 'কার্ডিনালিটি দেখান',
|
||||||
|
hide_field_attributes: 'ফিল্ড অ্যাট্রিবিউট লুকান',
|
||||||
|
show_field_attributes: 'ফিল্ড অ্যাট্রিবিউট দেখান',
|
||||||
zoom_on_scroll: 'স্ক্রলে জুম করুন',
|
zoom_on_scroll: 'স্ক্রলে জুম করুন',
|
||||||
|
show_views: 'ডাটাবেস ভিউ',
|
||||||
theme: 'থিম',
|
theme: 'থিম',
|
||||||
show_dependencies: 'নির্ভরতাগুলি দেখান',
|
show_dependencies: 'নির্ভরতাগুলি দেখান',
|
||||||
hide_dependencies: 'নির্ভরতাগুলি লুকান',
|
hide_dependencies: 'নির্ভরতাগুলি লুকান',
|
||||||
@@ -64,22 +75,13 @@ export const bn: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
|
title: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
|
||||||
description:
|
description:
|
||||||
'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?',
|
'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?',
|
||||||
reorder: 'পুনর্বিন্যাস করুন',
|
reorder: 'স্বয়ংক্রিয় সাজান',
|
||||||
cancel: 'বাতিল করুন',
|
cancel: 'বাতিল করুন',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'বহু স্কিমা',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} স্কিমা এই ডায়াগ্রামে রয়েছে। বর্তমানে প্রদর্শিত: {{formattedSchemas}}।',
|
|
||||||
dont_show_again: 'পুনরায় দেখাবেন না',
|
|
||||||
change_schema: 'পরিবর্তন করুন',
|
|
||||||
none: 'কিছুই না',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'কপি ব্যর্থ হয়েছে',
|
title: 'কপি ব্যর্থ হয়েছে',
|
||||||
@@ -114,14 +116,11 @@ export const bn: LanguageTranslation = {
|
|||||||
copied: 'অনুলিপি সম্পন্ন!',
|
copied: 'অনুলিপি সম্পন্ন!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'স্কিমা:',
|
|
||||||
filter_by_schema: 'স্কিমা দ্বারা ফিল্টার করুন',
|
|
||||||
search_schema: 'স্কিমা খুঁজুন...',
|
|
||||||
no_schemas_found: 'কোনো স্কিমা পাওয়া যায়নি।',
|
|
||||||
view_all_options: 'সমস্ত বিকল্প দেখুন...',
|
view_all_options: 'সমস্ত বিকল্প দেখুন...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'টেবিল',
|
tables: 'টেবিল',
|
||||||
add_table: 'টেবিল যোগ করুন',
|
add_table: 'টেবিল যোগ করুন',
|
||||||
|
add_view: 'ভিউ যোগ করুন',
|
||||||
filter: 'ফিল্টার',
|
filter: 'ফিল্টার',
|
||||||
collapse: 'সব ভাঁজ করুন',
|
collapse: 'সব ভাঁজ করুন',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -147,16 +146,23 @@ export const bn: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'ফিল্ড কর্ম',
|
title: 'ফিল্ড কর্ম',
|
||||||
unique: 'অদ্বিতীয়',
|
unique: 'অদ্বিতীয়',
|
||||||
|
auto_increment: 'স্বয়ংক্রিয় বৃদ্ধি',
|
||||||
comments: 'মন্তব্য',
|
comments: 'মন্তব্য',
|
||||||
no_comments: 'কোনো মন্তব্য নেই',
|
no_comments: 'কোনো মন্তব্য নেই',
|
||||||
delete_field: 'ফিল্ড মুছুন',
|
delete_field: 'ফিল্ড মুছুন',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'নির্ভুলতা',
|
||||||
|
scale: 'স্কেল',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'ইনডেক্স কর্ম',
|
title: 'ইনডেক্স কর্ম',
|
||||||
name: 'নাম',
|
name: 'নাম',
|
||||||
unique: 'অদ্বিতীয়',
|
unique: 'অদ্বিতীয়',
|
||||||
|
index_type: 'ইনডেক্স ধরন',
|
||||||
delete_index: 'ইনডেক্স মুছুন',
|
delete_index: 'ইনডেক্স মুছুন',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -173,14 +179,17 @@ export const bn: LanguageTranslation = {
|
|||||||
description: 'শুরু করতে একটি টেবিল তৈরি করুন',
|
description: 'শুরু করতে একটি টেবিল তৈরি করুন',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'সম্পর্ক',
|
refs: 'রেফস',
|
||||||
filter: 'ফিল্টার',
|
filter: 'ফিল্টার',
|
||||||
add_relationship: 'সম্পর্ক যোগ করুন',
|
|
||||||
collapse: 'সব ভাঁজ করুন',
|
collapse: 'সব ভাঁজ করুন',
|
||||||
|
add_relationship: 'সম্পর্ক যোগ করুন',
|
||||||
|
relationships: 'সম্পর্ক',
|
||||||
|
dependencies: 'নির্ভরতাগুলি',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'সম্পর্ক',
|
||||||
primary: 'প্রাথমিক টেবিল',
|
primary: 'প্রাথমিক টেবিল',
|
||||||
foreign: 'বিদেশি টেবিল',
|
foreign: 'রেফারেন্স করা টেবিল',
|
||||||
cardinality: 'কার্ডিনালিটি',
|
cardinality: 'কার্ডিনালিটি',
|
||||||
delete_relationship: 'মুছুন',
|
delete_relationship: 'মুছুন',
|
||||||
relationship_actions: {
|
relationship_actions: {
|
||||||
@@ -188,27 +197,19 @@ export const bn: LanguageTranslation = {
|
|||||||
delete_relationship: 'মুছুন',
|
delete_relationship: 'মুছুন',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'কোনো সম্পর্ক নেই',
|
|
||||||
description: 'টেবিল সংযোগ করতে একটি সম্পর্ক তৈরি করুন',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'নির্ভরতাগুলি',
|
|
||||||
filter: 'ফিল্টার',
|
|
||||||
collapse: 'ভাঁজ করুন',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'নির্ভরতা',
|
||||||
table: 'টেবিল',
|
table: 'টেবিল',
|
||||||
dependent_table: 'নির্ভরশীল টেবিল',
|
dependent_table: 'নির্ভরশীল ভিউ',
|
||||||
delete_dependency: 'নির্ভরতা মুছুন',
|
delete_dependency: 'মুছুন',
|
||||||
dependency_actions: {
|
dependency_actions: {
|
||||||
title: 'কর্ম',
|
title: 'কর্ম',
|
||||||
delete_dependency: 'নির্ভরতা মুছুন',
|
delete_dependency: 'মুছুন',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'কোনো নির্ভরতাগুলি নেই',
|
title: 'কোনো সম্পর্ক নেই',
|
||||||
description: 'এই অংশে কোনো নির্ভরতা উপলব্ধ নেই।',
|
description: 'শুরু করতে একটি সম্পর্ক তৈরি করুন',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -232,6 +233,35 @@ export const bn: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'ভিজ্যুয়াল',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'নোট',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'ফিল্টার',
|
||||||
|
add_note: 'নোট যোগ করুন',
|
||||||
|
no_results: 'কোনো নোট পাওয়া যায়নি',
|
||||||
|
clear: 'ফিল্টার সাফ করুন',
|
||||||
|
empty_state: {
|
||||||
|
title: 'কোনো নোট নেই',
|
||||||
|
description:
|
||||||
|
'ক্যানভাসে টেক্সট টীকা যোগ করতে একটি নোট তৈরি করুন',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'খালি নোট',
|
||||||
|
note_actions: {
|
||||||
|
title: 'নোট ক্রিয়া',
|
||||||
|
edit_content: 'বিষয়বস্তু সম্পাদনা',
|
||||||
|
delete_note: 'নোট মুছুন',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -248,12 +278,16 @@ export const bn: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'কোন enum মান সংজ্ঞায়িত নেই',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -267,8 +301,14 @@ export const bn: LanguageTranslation = {
|
|||||||
show_all: 'সব দেখান',
|
show_all: 'সব দেখান',
|
||||||
undo: 'পূর্বাবস্থায় ফিরুন',
|
undo: 'পূর্বাবস্থায় ফিরুন',
|
||||||
redo: 'পুনরায় করুন',
|
redo: 'পুনরায় করুন',
|
||||||
reorder_diagram: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
|
reorder_diagram: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
|
||||||
highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন',
|
highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন',
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -299,13 +339,13 @@ export const bn: LanguageTranslation = {
|
|||||||
cancel: 'বাতিল করুন',
|
cancel: 'বাতিল করুন',
|
||||||
back: 'ফিরে যান',
|
back: 'ফিরে যান',
|
||||||
import_from_file: 'ফাইল থেকে আমদানি করুন',
|
import_from_file: 'ফাইল থেকে আমদানি করুন',
|
||||||
empty_diagram: 'ফাঁকা চিত্র',
|
empty_diagram: 'খালি ডাটাবেস',
|
||||||
continue: 'চালিয়ে যান',
|
continue: 'চালিয়ে যান',
|
||||||
import: 'আমদানি করুন',
|
import: 'আমদানি করুন',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'চিত্র খুলুন',
|
title: 'ডেটাবেস খুলুন',
|
||||||
description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।',
|
description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'নাম',
|
name: 'নাম',
|
||||||
@@ -315,6 +355,12 @@ export const bn: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'বাতিল করুন',
|
cancel: 'বাতিল করুন',
|
||||||
open: 'খুলুন',
|
open: 'খুলুন',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'খুলুন',
|
||||||
|
duplicate: 'ডুপ্লিকেট',
|
||||||
|
delete: 'মুছুন',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -400,6 +446,13 @@ export const bn: LanguageTranslation = {
|
|||||||
cancel: 'বাতিল করুন',
|
cancel: 'বাতিল করুন',
|
||||||
confirm: 'পরিবর্তন করুন',
|
confirm: 'পরিবর্তন করুন',
|
||||||
},
|
},
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'নতুন স্কিমা তৈরি করুন',
|
||||||
|
description:
|
||||||
|
'এখনও কোনো স্কিমা নেই। আপনার টেবিলগুলি সংগঠিত করতে আপনার প্রথম স্কিমা তৈরি করুন।',
|
||||||
|
create: 'তৈরি করুন',
|
||||||
|
cancel: 'বাতিল করুন',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'আমাদের উন্নত করতে সাহায্য করুন!',
|
title: 'আমাদের উন্নত করতে সাহায্য করুন!',
|
||||||
@@ -456,9 +509,11 @@ export const bn: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'নতুন টেবিল',
|
new_table: 'নতুন টেবিল',
|
||||||
|
new_view: 'নতুন ভিউ',
|
||||||
new_relationship: 'নতুন সম্পর্ক',
|
new_relationship: 'নতুন সম্পর্ক',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'নতুন নোট',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -477,6 +532,9 @@ export const bn: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'ভাষা পরিবর্তন করুন',
|
change_language: 'ভাষা পরিবর্তন করুন',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'চালু',
|
||||||
|
off: 'বন্ধ',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const de: LanguageTranslation = {
|
export const de: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Neu',
|
||||||
|
browse: 'Durchsuchen',
|
||||||
|
tables: 'Tabellen',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Abhängigkeiten',
|
||||||
|
custom_types: 'Benutzerdefinierte Typen',
|
||||||
|
visuals: 'Darstellungen',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Datei',
|
actions: 'Aktionen',
|
||||||
new: 'Neu',
|
new: 'Neu...',
|
||||||
open: 'Öffnen',
|
browse: 'Durchsuchen...',
|
||||||
save: 'Speichern',
|
save: 'Speichern',
|
||||||
import: 'Datenbank importieren',
|
import: 'Datenbank importieren',
|
||||||
export_sql: 'SQL exportieren',
|
export_sql: 'SQL exportieren',
|
||||||
export_as: 'Exportieren als',
|
export_as: 'Exportieren als',
|
||||||
delete_diagram: 'Diagramm löschen',
|
delete_diagram: 'Löschen',
|
||||||
exit: 'Beenden',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Bearbeiten',
|
edit: 'Bearbeiten',
|
||||||
@@ -26,7 +34,10 @@ export const de: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Seitenleiste ausblenden',
|
hide_sidebar: 'Seitenleiste ausblenden',
|
||||||
hide_cardinality: 'Kardinalität ausblenden',
|
hide_cardinality: 'Kardinalität ausblenden',
|
||||||
show_cardinality: 'Kardinalität anzeigen',
|
show_cardinality: 'Kardinalität anzeigen',
|
||||||
|
hide_field_attributes: 'Feldattribute ausblenden',
|
||||||
|
show_field_attributes: 'Feldattribute anzeigen',
|
||||||
zoom_on_scroll: 'Zoom beim Scrollen',
|
zoom_on_scroll: 'Zoom beim Scrollen',
|
||||||
|
show_views: 'Datenbankansichten',
|
||||||
theme: 'Stil',
|
theme: 'Stil',
|
||||||
show_dependencies: 'Abhängigkeiten anzeigen',
|
show_dependencies: 'Abhängigkeiten anzeigen',
|
||||||
hide_dependencies: 'Abhängigkeiten ausblenden',
|
hide_dependencies: 'Abhängigkeiten ausblenden',
|
||||||
@@ -64,22 +75,13 @@ export const de: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Diagramm neu anordnen',
|
title: 'Diagramm automatisch anordnen',
|
||||||
description:
|
description:
|
||||||
'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?',
|
'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?',
|
||||||
reorder: 'Neu anordnen',
|
reorder: 'Automatisch anordnen',
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Mehrere Schemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} Schemas in diesem Diagramm. Derzeit angezeigt: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Nicht erneut anzeigen',
|
|
||||||
change_schema: 'Schema ändern',
|
|
||||||
none: 'Keine',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Kopieren fehlgeschlagen',
|
title: 'Kopieren fehlgeschlagen',
|
||||||
@@ -115,14 +117,11 @@ export const de: LanguageTranslation = {
|
|||||||
copied: 'Kopiert!',
|
copied: 'Kopiert!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schema:',
|
|
||||||
filter_by_schema: 'Nach Schema filtern',
|
|
||||||
search_schema: 'Schema suchen...',
|
|
||||||
no_schemas_found: 'Keine Schemas gefunden.',
|
|
||||||
view_all_options: 'Alle Optionen anzeigen...',
|
view_all_options: 'Alle Optionen anzeigen...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tabellen',
|
tables: 'Tabellen',
|
||||||
add_table: 'Tabelle hinzufügen',
|
add_table: 'Tabelle hinzufügen',
|
||||||
|
add_view: 'Ansicht hinzufügen',
|
||||||
filter: 'Filter',
|
filter: 'Filter',
|
||||||
collapse: 'Alle einklappen',
|
collapse: 'Alle einklappen',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -148,16 +147,23 @@ export const de: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Feldattribute',
|
title: 'Feldattribute',
|
||||||
unique: 'Eindeutig',
|
unique: 'Eindeutig',
|
||||||
|
auto_increment: 'Automatisch hochzählen',
|
||||||
comments: 'Kommentare',
|
comments: 'Kommentare',
|
||||||
no_comments: 'Keine Kommentare',
|
no_comments: 'Keine Kommentare',
|
||||||
delete_field: 'Feld löschen',
|
delete_field: 'Feld löschen',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Präzision',
|
||||||
|
scale: 'Skalierung',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Indexattribute',
|
title: 'Indexattribute',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
unique: 'Eindeutig',
|
unique: 'Eindeutig',
|
||||||
|
index_type: 'Indextyp',
|
||||||
delete_index: 'Index löschen',
|
delete_index: 'Index löschen',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -174,32 +180,26 @@ export const de: LanguageTranslation = {
|
|||||||
description: 'Erstellen Sie eine Tabelle, um zu beginnen',
|
description: 'Erstellen Sie eine Tabelle, um zu beginnen',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Beziehungen',
|
refs: 'Refs',
|
||||||
filter: 'Filter',
|
filter: 'Filter',
|
||||||
add_relationship: 'Beziehung hinzufügen',
|
|
||||||
collapse: 'Alle einklappen',
|
collapse: 'Alle einklappen',
|
||||||
|
add_relationship: 'Beziehung hinzufügen',
|
||||||
|
relationships: 'Beziehungen',
|
||||||
|
dependencies: 'Abhängigkeiten',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Beziehung',
|
||||||
primary: 'Primäre Tabelle',
|
primary: 'Primäre Tabelle',
|
||||||
foreign: 'Referenzierte Tabelle',
|
foreign: 'Referenzierte Tabelle',
|
||||||
cardinality: 'Kardinalität',
|
cardinality: 'Kardinalität',
|
||||||
delete_relationship: 'Beziehung löschen',
|
delete_relationship: 'Löschen',
|
||||||
relationship_actions: {
|
relationship_actions: {
|
||||||
title: 'Aktionen',
|
title: 'Aktionen',
|
||||||
delete_relationship: 'Beziehung löschen',
|
delete_relationship: 'Löschen',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'Keine Beziehungen',
|
|
||||||
description:
|
|
||||||
'Erstellen Sie eine Beziehung, um Tabellen zu verbinden',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Abhängigkeiten',
|
|
||||||
filter: 'Filter',
|
|
||||||
collapse: 'Alle einklappen',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Abhängigkeit',
|
||||||
table: 'Tabelle',
|
table: 'Tabelle',
|
||||||
dependent_table: 'Abhängige Ansicht',
|
dependent_table: 'Abhängige Ansicht',
|
||||||
delete_dependency: 'Löschen',
|
delete_dependency: 'Löschen',
|
||||||
@@ -209,8 +209,8 @@ export const de: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Keine Abhängigkeiten',
|
title: 'Keine Beziehungen',
|
||||||
description: 'Erstellen Sie eine Ansicht, um zu beginnen',
|
description: 'Erstellen Sie eine Beziehung, um zu beginnen',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -234,6 +234,35 @@ export const de: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Darstellungen',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Notizen',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filter',
|
||||||
|
add_note: 'Notiz hinzufügen',
|
||||||
|
no_results: 'Keine Notizen gefunden',
|
||||||
|
clear: 'Filter löschen',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Keine Notizen',
|
||||||
|
description:
|
||||||
|
'Erstellen Sie eine Notiz, um Textanmerkungen auf der Leinwand hinzuzufügen',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Leere Notiz',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Notiz-Aktionen',
|
||||||
|
edit_content: 'Inhalt bearbeiten',
|
||||||
|
delete_note: 'Notiz löschen',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -250,12 +279,16 @@ export const de: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'Keine Enum-Werte definiert',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -269,8 +302,15 @@ export const de: LanguageTranslation = {
|
|||||||
show_all: 'Alle anzeigen',
|
show_all: 'Alle anzeigen',
|
||||||
undo: 'Rückgängig',
|
undo: 'Rückgängig',
|
||||||
redo: 'Wiederholen',
|
redo: 'Wiederholen',
|
||||||
reorder_diagram: 'Diagramm neu anordnen',
|
reorder_diagram: 'Diagramm automatisch anordnen',
|
||||||
|
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Überlappende Tabellen hervorheben',
|
highlight_overlapping_tables: 'Überlappende Tabellen hervorheben',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -302,13 +342,13 @@ export const de: LanguageTranslation = {
|
|||||||
back: 'Zurück',
|
back: 'Zurück',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'Leeres Diagramm',
|
empty_diagram: 'Leere Datenbank',
|
||||||
continue: 'Weiter',
|
continue: 'Weiter',
|
||||||
import: 'Importieren',
|
import: 'Importieren',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Diagramm öffnen',
|
title: 'Datenbank öffnen',
|
||||||
description: 'Wählen Sie ein Diagramm aus der Liste unten aus.',
|
description: 'Wählen Sie ein Diagramm aus der Liste unten aus.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
@@ -318,6 +358,12 @@ export const de: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
open: 'Öffnen',
|
open: 'Öffnen',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Öffnen',
|
||||||
|
duplicate: 'Duplizieren',
|
||||||
|
delete: 'Löschen',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -403,6 +449,13 @@ export const de: LanguageTranslation = {
|
|||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
confirm: 'Ändern',
|
confirm: 'Ändern',
|
||||||
},
|
},
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Neues Schema erstellen',
|
||||||
|
description:
|
||||||
|
'Es existieren noch keine Schemas. Erstellen Sie Ihr erstes Schema, um Ihre Tabellen zu organisieren.',
|
||||||
|
create: 'Erstellen',
|
||||||
|
cancel: 'Abbrechen',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Hilf uns, uns zu verbessern!',
|
title: 'Hilf uns, uns zu verbessern!',
|
||||||
@@ -459,9 +512,11 @@ export const de: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Neue Tabelle',
|
new_table: 'Neue Tabelle',
|
||||||
|
new_view: 'Neue Ansicht',
|
||||||
new_relationship: 'Neue Beziehung',
|
new_relationship: 'Neue Beziehung',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Neue Notiz',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -481,6 +536,9 @@ export const de: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Sprache',
|
change_language: 'Sprache',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Ein',
|
||||||
|
off: 'Aus',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata } from '../types';
|
|||||||
|
|
||||||
export const en = {
|
export const en = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'New',
|
||||||
|
browse: 'Browse',
|
||||||
|
tables: 'Tables',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Dependencies',
|
||||||
|
custom_types: 'Custom Types',
|
||||||
|
visuals: 'Visuals',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'File',
|
actions: 'Actions',
|
||||||
new: 'New',
|
new: 'New...',
|
||||||
open: 'Open',
|
browse: 'Browse...',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
export_sql: 'Export SQL',
|
export_sql: 'Export SQL',
|
||||||
export_as: 'Export as',
|
export_as: 'Export as',
|
||||||
delete_diagram: 'Delete Diagram',
|
delete_diagram: 'Delete',
|
||||||
exit: 'Exit',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
@@ -26,7 +34,10 @@ export const en = {
|
|||||||
hide_sidebar: 'Hide Sidebar',
|
hide_sidebar: 'Hide Sidebar',
|
||||||
hide_cardinality: 'Hide Cardinality',
|
hide_cardinality: 'Hide Cardinality',
|
||||||
show_cardinality: 'Show Cardinality',
|
show_cardinality: 'Show Cardinality',
|
||||||
|
hide_field_attributes: 'Hide Field Attributes',
|
||||||
|
show_field_attributes: 'Show Field Attributes',
|
||||||
zoom_on_scroll: 'Zoom on Scroll',
|
zoom_on_scroll: 'Zoom on Scroll',
|
||||||
|
show_views: 'Database Views',
|
||||||
theme: 'Theme',
|
theme: 'Theme',
|
||||||
show_dependencies: 'Show Dependencies',
|
show_dependencies: 'Show Dependencies',
|
||||||
hide_dependencies: 'Hide Dependencies',
|
hide_dependencies: 'Hide Dependencies',
|
||||||
@@ -62,22 +73,13 @@ export const en = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Reorder Diagram',
|
title: 'Auto Arrange Diagram',
|
||||||
description:
|
description:
|
||||||
'This action will rearrange all tables in the diagram. Do you want to continue?',
|
'This action will rearrange all tables in the diagram. Do you want to continue?',
|
||||||
reorder: 'Reorder',
|
reorder: 'Auto Arrange',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Multiple Schemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} schemas in this diagram. Currently displaying: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: "Don't show again",
|
|
||||||
change_schema: 'Change',
|
|
||||||
none: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Copy failed',
|
title: 'Copy failed',
|
||||||
@@ -112,14 +114,11 @@ export const en = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schema:',
|
|
||||||
filter_by_schema: 'Filter by schema',
|
|
||||||
search_schema: 'Search schema...',
|
|
||||||
no_schemas_found: 'No schemas found.',
|
|
||||||
view_all_options: 'View all Options...',
|
view_all_options: 'View all Options...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tables',
|
tables: 'Tables',
|
||||||
add_table: 'Add Table',
|
add_table: 'Add Table',
|
||||||
|
add_view: 'Add View',
|
||||||
filter: 'Filter',
|
filter: 'Filter',
|
||||||
collapse: 'Collapse All',
|
collapse: 'Collapse All',
|
||||||
clear: 'Clear Filter',
|
clear: 'Clear Filter',
|
||||||
@@ -143,15 +142,21 @@ export const en = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Field Attributes',
|
title: 'Field Attributes',
|
||||||
unique: 'Unique',
|
unique: 'Unique',
|
||||||
|
auto_increment: 'Auto Increment',
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Precision',
|
||||||
|
scale: 'Scale',
|
||||||
comments: 'Comments',
|
comments: 'Comments',
|
||||||
no_comments: 'No comments',
|
no_comments: 'No comments',
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
delete_field: 'Delete Field',
|
delete_field: 'Delete Field',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Index Attributes',
|
title: 'Index Attributes',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
unique: 'Unique',
|
unique: 'Unique',
|
||||||
|
index_type: 'Index Type',
|
||||||
delete_index: 'Delete Index',
|
delete_index: 'Delete Index',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -168,12 +173,15 @@ export const en = {
|
|||||||
description: 'Create a table to get started',
|
description: 'Create a table to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Relationships',
|
refs: 'Refs',
|
||||||
filter: 'Filter',
|
filter: 'Filter',
|
||||||
add_relationship: 'Add Relationship',
|
|
||||||
collapse: 'Collapse All',
|
collapse: 'Collapse All',
|
||||||
|
add_relationship: 'Add Relationship',
|
||||||
|
relationships: 'Relationships',
|
||||||
|
dependencies: 'Dependencies',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Relationship',
|
||||||
primary: 'Primary Table',
|
primary: 'Primary Table',
|
||||||
foreign: 'Referenced Table',
|
foreign: 'Referenced Table',
|
||||||
cardinality: 'Cardinality',
|
cardinality: 'Cardinality',
|
||||||
@@ -183,16 +191,8 @@ export const en = {
|
|||||||
delete_relationship: 'Delete',
|
delete_relationship: 'Delete',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'No relationships',
|
|
||||||
description: 'Create a relationship to connect tables',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Dependencies',
|
|
||||||
filter: 'Filter',
|
|
||||||
collapse: 'Collapse All',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Dependency',
|
||||||
table: 'Table',
|
table: 'Table',
|
||||||
dependent_table: 'Dependent View',
|
dependent_table: 'Dependent View',
|
||||||
delete_dependency: 'Delete',
|
delete_dependency: 'Delete',
|
||||||
@@ -202,8 +202,8 @@ export const en = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'No dependencies',
|
title: 'No relationships',
|
||||||
description: 'Create a view to get started',
|
description: 'Create a relationship to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -227,6 +227,34 @@ export const en = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuals',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Notes',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filter',
|
||||||
|
add_note: 'Add Note',
|
||||||
|
no_results: 'No notes found',
|
||||||
|
clear: 'Clear Filter',
|
||||||
|
empty_state: {
|
||||||
|
title: 'No Notes',
|
||||||
|
description:
|
||||||
|
'Create a note to add text annotations on the canvas',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Empty note',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Note Actions',
|
||||||
|
edit_content: 'Edit Content',
|
||||||
|
delete_note: 'Delete Note',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
filter: 'Filter',
|
filter: 'Filter',
|
||||||
@@ -242,11 +270,15 @@ export const en = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'No enum values defined',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
@@ -261,8 +293,12 @@ export const en = {
|
|||||||
show_all: 'Show All',
|
show_all: 'Show All',
|
||||||
undo: 'Undo',
|
undo: 'Undo',
|
||||||
redo: 'Redo',
|
redo: 'Redo',
|
||||||
reorder_diagram: 'Reorder Diagram',
|
reorder_diagram: 'Auto Arrange Diagram',
|
||||||
highlight_overlapping_tables: 'Highlight Overlapping Tables',
|
highlight_overlapping_tables: 'Highlight Overlapping Tables',
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -293,13 +329,13 @@ export const en = {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
empty_diagram: 'Empty diagram',
|
empty_diagram: 'Empty database',
|
||||||
continue: 'Continue',
|
continue: 'Continue',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Open Diagram',
|
title: 'Open Database',
|
||||||
description: 'Select a diagram to open from the list below.',
|
description: 'Select a diagram to open from the list below.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
@@ -309,6 +345,12 @@ export const en = {
|
|||||||
},
|
},
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
open: 'Open',
|
open: 'Open',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Open',
|
||||||
|
duplicate: 'Duplicate',
|
||||||
|
delete: 'Delete',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -394,6 +436,14 @@ export const en = {
|
|||||||
confirm: 'Change',
|
confirm: 'Change',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Create New Schema',
|
||||||
|
description:
|
||||||
|
'No schemas exist yet. Create your first schema to organize your tables.',
|
||||||
|
create: 'Create',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Help us improve!',
|
title: 'Help us improve!',
|
||||||
description:
|
description:
|
||||||
@@ -448,8 +498,10 @@ export const en = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'New Table',
|
new_table: 'New Table',
|
||||||
|
new_view: 'New View',
|
||||||
new_relationship: 'New Relationship',
|
new_relationship: 'New Relationship',
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'New Note',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -468,6 +520,9 @@ export const en = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Language',
|
change_language: 'Language',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'On',
|
||||||
|
off: 'Off',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const es: LanguageTranslation = {
|
export const es: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Nuevo',
|
||||||
|
browse: 'Examinar',
|
||||||
|
tables: 'Tablas',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Dependencias',
|
||||||
|
custom_types: 'Tipos Personalizados',
|
||||||
|
visuals: 'Visuales',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Archivo',
|
actions: 'Acciones',
|
||||||
new: 'Nuevo',
|
new: 'Nuevo...',
|
||||||
open: 'Abrir',
|
browse: 'Examinar...',
|
||||||
save: 'Guardar',
|
save: 'Guardar',
|
||||||
import: 'Importar Base de Datos',
|
import: 'Importar Base de Datos',
|
||||||
export_sql: 'Exportar SQL',
|
export_sql: 'Exportar SQL',
|
||||||
export_as: 'Exportar como',
|
export_as: 'Exportar como',
|
||||||
delete_diagram: 'Eliminar Diagrama',
|
delete_diagram: 'Eliminar',
|
||||||
exit: 'Salir',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Editar',
|
edit: 'Editar',
|
||||||
@@ -24,9 +32,12 @@ export const es: LanguageTranslation = {
|
|||||||
view: 'Ver',
|
view: 'Ver',
|
||||||
hide_cardinality: 'Ocultar Cardinalidad',
|
hide_cardinality: 'Ocultar Cardinalidad',
|
||||||
show_cardinality: 'Mostrar Cardinalidad',
|
show_cardinality: 'Mostrar Cardinalidad',
|
||||||
|
show_field_attributes: 'Mostrar Atributos de Campo',
|
||||||
|
hide_field_attributes: 'Ocultar Atributos de Campo',
|
||||||
show_sidebar: 'Mostrar Barra Lateral',
|
show_sidebar: 'Mostrar Barra Lateral',
|
||||||
hide_sidebar: 'Ocultar Barra Lateral',
|
hide_sidebar: 'Ocultar Barra Lateral',
|
||||||
zoom_on_scroll: 'Zoom al Desplazarse',
|
zoom_on_scroll: 'Zoom al Desplazarse',
|
||||||
|
show_views: 'Vistas de Base de Datos',
|
||||||
theme: 'Tema',
|
theme: 'Tema',
|
||||||
show_dependencies: 'Mostrar dependencias',
|
show_dependencies: 'Mostrar dependencias',
|
||||||
hide_dependencies: 'Ocultar dependencias',
|
hide_dependencies: 'Ocultar dependencias',
|
||||||
@@ -63,10 +74,10 @@ export const es: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Reordenar Diagrama',
|
title: 'Organizar Diagrama Automáticamente',
|
||||||
description:
|
description:
|
||||||
'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?',
|
'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?',
|
||||||
reorder: 'Reordenar',
|
reorder: 'Organizar Automáticamente',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -104,14 +115,11 @@ export const es: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Esquema:',
|
|
||||||
filter_by_schema: 'Filtrar por esquema',
|
|
||||||
search_schema: 'Buscar esquema...',
|
|
||||||
no_schemas_found: 'No se encontraron esquemas.',
|
|
||||||
view_all_options: 'Ver todas las opciones...',
|
view_all_options: 'Ver todas las opciones...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tablas',
|
tables: 'Tablas',
|
||||||
add_table: 'Agregar Tabla',
|
add_table: 'Agregar Tabla',
|
||||||
|
add_view: 'Agregar Vista',
|
||||||
filter: 'Filtrar',
|
filter: 'Filtrar',
|
||||||
collapse: 'Colapsar Todo',
|
collapse: 'Colapsar Todo',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -137,16 +145,23 @@ export const es: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Atributos del Campo',
|
title: 'Atributos del Campo',
|
||||||
unique: 'Único',
|
unique: 'Único',
|
||||||
|
auto_increment: 'Autoincremento',
|
||||||
comments: 'Comentarios',
|
comments: 'Comentarios',
|
||||||
no_comments: 'Sin comentarios',
|
no_comments: 'Sin comentarios',
|
||||||
delete_field: 'Eliminar Campo',
|
delete_field: 'Eliminar Campo',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Precisión',
|
||||||
|
scale: 'Escala',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Atributos del Índice',
|
title: 'Atributos del Índice',
|
||||||
name: 'Nombre',
|
name: 'Nombre',
|
||||||
unique: 'Único',
|
unique: 'Único',
|
||||||
|
index_type: 'Tipo de Índice',
|
||||||
delete_index: 'Eliminar Índice',
|
delete_index: 'Eliminar Índice',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -163,14 +178,17 @@ export const es: LanguageTranslation = {
|
|||||||
description: 'Crea una tabla para comenzar',
|
description: 'Crea una tabla para comenzar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Relaciones',
|
refs: 'Refs',
|
||||||
add_relationship: 'Agregar Relación',
|
|
||||||
filter: 'Filtrar',
|
filter: 'Filtrar',
|
||||||
collapse: 'Colapsar Todo',
|
collapse: 'Colapsar Todo',
|
||||||
|
add_relationship: 'Agregar Relación',
|
||||||
|
relationships: 'Relaciones',
|
||||||
|
dependencies: 'Dependencias',
|
||||||
relationship: {
|
relationship: {
|
||||||
primary: 'Primaria',
|
relationship: 'Relación',
|
||||||
foreign: 'Foránea',
|
primary: 'Tabla Primaria',
|
||||||
|
foreign: 'Tabla Referenciada',
|
||||||
cardinality: 'Cardinalidad',
|
cardinality: 'Cardinalidad',
|
||||||
delete_relationship: 'Eliminar',
|
delete_relationship: 'Eliminar',
|
||||||
relationship_actions: {
|
relationship_actions: {
|
||||||
@@ -178,18 +196,10 @@ export const es: LanguageTranslation = {
|
|||||||
delete_relationship: 'Eliminar',
|
delete_relationship: 'Eliminar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'No hay relaciones',
|
|
||||||
description: 'Crea una relación para conectar tablas',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Dependencias',
|
|
||||||
filter: 'Filtro',
|
|
||||||
collapse: 'Colapsar todo',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Dependencia',
|
||||||
table: 'Tabla',
|
table: 'Tabla',
|
||||||
dependent_table: 'Vista dependiente',
|
dependent_table: 'Vista Dependiente',
|
||||||
delete_dependency: 'Eliminar',
|
delete_dependency: 'Eliminar',
|
||||||
dependency_actions: {
|
dependency_actions: {
|
||||||
title: 'Acciones',
|
title: 'Acciones',
|
||||||
@@ -197,8 +207,8 @@ export const es: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Sin dependencias',
|
title: 'Sin relaciones',
|
||||||
description: 'Crea una vista para comenzar',
|
description: 'Crea una relación para comenzar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -222,6 +232,35 @@ export const es: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuales',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Notas',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filtrar',
|
||||||
|
add_note: 'Agregar Nota',
|
||||||
|
no_results: 'No se encontraron notas',
|
||||||
|
clear: 'Limpiar Filtro',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Sin Notas',
|
||||||
|
description:
|
||||||
|
'Crea una nota para agregar anotaciones de texto en el lienzo',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Nota vacía',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Acciones de Nota',
|
||||||
|
edit_content: 'Editar Contenido',
|
||||||
|
delete_note: 'Eliminar Nota',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -238,12 +277,16 @@ export const es: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'No hay valores de enum definidos',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -257,8 +300,14 @@ export const es: LanguageTranslation = {
|
|||||||
show_all: 'Mostrar Todo',
|
show_all: 'Mostrar Todo',
|
||||||
undo: 'Deshacer',
|
undo: 'Deshacer',
|
||||||
redo: 'Rehacer',
|
redo: 'Rehacer',
|
||||||
reorder_diagram: 'Reordenar Diagrama',
|
reorder_diagram: 'Organizar Diagrama Automáticamente',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Resaltar tablas superpuestas',
|
highlight_overlapping_tables: 'Resaltar tablas superpuestas',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -290,13 +339,13 @@ export const es: LanguageTranslation = {
|
|||||||
back: 'Atrás',
|
back: 'Atrás',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'Diagrama vacío',
|
empty_diagram: 'Base de datos vacía',
|
||||||
continue: 'Continuar',
|
continue: 'Continuar',
|
||||||
import: 'Importar',
|
import: 'Importar',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Abrir Diagrama',
|
title: 'Abrir Base de Datos',
|
||||||
description:
|
description:
|
||||||
'Selecciona un diagrama para abrir de la lista a continuación.',
|
'Selecciona un diagrama para abrir de la lista a continuación.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
@@ -307,6 +356,12 @@ export const es: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
open: 'Abrir',
|
open: 'Abrir',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Abrir',
|
||||||
|
duplicate: 'Duplicar',
|
||||||
|
delete: 'Eliminar',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -392,6 +447,13 @@ export const es: LanguageTranslation = {
|
|||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
confirm: 'Cambiar',
|
confirm: 'Cambiar',
|
||||||
},
|
},
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Crear Nuevo Esquema',
|
||||||
|
description:
|
||||||
|
'Aún no existen esquemas. Crea tu primer esquema para organizar tus tablas.',
|
||||||
|
create: 'Crear',
|
||||||
|
cancel: 'Cancelar',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: '¡Ayúdanos a mejorar!',
|
title: '¡Ayúdanos a mejorar!',
|
||||||
@@ -401,14 +463,6 @@ export const es: LanguageTranslation = {
|
|||||||
confirm: '¡Claro!',
|
confirm: '¡Claro!',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Múltiples Esquemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} esquemas en este diagrama. Actualmente mostrando: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'No mostrar de nuevo',
|
|
||||||
change_schema: 'Cambiar',
|
|
||||||
none: 'nada',
|
|
||||||
},
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
export_diagram_dialog: {
|
export_diagram_dialog: {
|
||||||
title: 'Export Diagram',
|
title: 'Export Diagram',
|
||||||
@@ -457,9 +511,11 @@ export const es: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Nueva Tabla',
|
new_table: 'Nueva Tabla',
|
||||||
|
new_view: 'Nueva Vista',
|
||||||
new_relationship: 'Nueva Relación',
|
new_relationship: 'Nueva Relación',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Nueva Nota',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -479,6 +535,9 @@ export const es: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Idioma',
|
change_language: 'Idioma',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Encendido',
|
||||||
|
off: 'Apagado',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const fr: LanguageTranslation = {
|
export const fr: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Nouveau',
|
||||||
|
browse: 'Parcourir',
|
||||||
|
tables: 'Tables',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Dépendances',
|
||||||
|
custom_types: 'Types Personnalisés',
|
||||||
|
visuals: 'Visuels',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Fichier',
|
actions: 'Actions',
|
||||||
new: 'Nouveau',
|
new: 'Nouveau...',
|
||||||
open: 'Ouvrir',
|
browse: 'Parcourir...',
|
||||||
save: 'Enregistrer',
|
save: 'Enregistrer',
|
||||||
import: 'Importer Base de Données',
|
import: 'Importer Base de Données',
|
||||||
export_sql: 'Exporter SQL',
|
export_sql: 'Exporter SQL',
|
||||||
export_as: 'Exporter en tant que',
|
export_as: 'Exporter en tant que',
|
||||||
delete_diagram: 'Supprimer le Diagramme',
|
delete_diagram: 'Supprimer',
|
||||||
exit: 'Quitter',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Édition',
|
edit: 'Édition',
|
||||||
@@ -26,7 +34,10 @@ export const fr: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Cacher la Barre Latérale',
|
hide_sidebar: 'Cacher la Barre Latérale',
|
||||||
hide_cardinality: 'Cacher la Cardinalité',
|
hide_cardinality: 'Cacher la Cardinalité',
|
||||||
show_cardinality: 'Afficher la Cardinalité',
|
show_cardinality: 'Afficher la Cardinalité',
|
||||||
|
hide_field_attributes: 'Masquer les Attributs de Champ',
|
||||||
|
show_field_attributes: 'Afficher les Attributs de Champ',
|
||||||
zoom_on_scroll: 'Zoom sur le Défilement',
|
zoom_on_scroll: 'Zoom sur le Défilement',
|
||||||
|
show_views: 'Vues de Base de Données',
|
||||||
theme: 'Thème',
|
theme: 'Thème',
|
||||||
show_dependencies: 'Afficher les Dépendances',
|
show_dependencies: 'Afficher les Dépendances',
|
||||||
hide_dependencies: 'Masquer les Dépendances',
|
hide_dependencies: 'Masquer les Dépendances',
|
||||||
@@ -62,10 +73,10 @@ export const fr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Réorganiser le Diagramme',
|
title: 'Organiser Automatiquement le Diagramme',
|
||||||
description:
|
description:
|
||||||
'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?',
|
'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?',
|
||||||
reorder: 'Réorganiser',
|
reorder: 'Organiser Automatiquement',
|
||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -103,14 +114,11 @@ export const fr: LanguageTranslation = {
|
|||||||
copied: 'Copié !',
|
copied: 'Copié !',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schéma:',
|
|
||||||
filter_by_schema: 'Filtrer par schéma',
|
|
||||||
search_schema: 'Rechercher un schéma...',
|
|
||||||
no_schemas_found: 'Aucun schéma trouvé.',
|
|
||||||
view_all_options: 'Voir toutes les Options...',
|
view_all_options: 'Voir toutes les Options...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tables',
|
tables: 'Tables',
|
||||||
add_table: 'Ajouter une Table',
|
add_table: 'Ajouter une Table',
|
||||||
|
add_view: 'Ajouter une Vue',
|
||||||
filter: 'Filtrer',
|
filter: 'Filtrer',
|
||||||
collapse: 'Réduire Tout',
|
collapse: 'Réduire Tout',
|
||||||
clear: 'Effacer le Filtre',
|
clear: 'Effacer le Filtre',
|
||||||
@@ -135,16 +143,23 @@ export const fr: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Attributs du Champ',
|
title: 'Attributs du Champ',
|
||||||
unique: 'Unique',
|
unique: 'Unique',
|
||||||
|
auto_increment: 'Auto-incrément',
|
||||||
comments: 'Commentaires',
|
comments: 'Commentaires',
|
||||||
no_comments: 'Pas de commentaires',
|
no_comments: 'Pas de commentaires',
|
||||||
delete_field: 'Supprimer le Champ',
|
delete_field: 'Supprimer le Champ',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Précision',
|
||||||
|
scale: 'Échelle',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: "Attributs de l'Index",
|
title: "Attributs de l'Index",
|
||||||
name: 'Nom',
|
name: 'Nom',
|
||||||
unique: 'Unique',
|
unique: 'Unique',
|
||||||
|
index_type: "Type d'index",
|
||||||
delete_index: "Supprimer l'Index",
|
delete_index: "Supprimer l'Index",
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -161,12 +176,15 @@ export const fr: LanguageTranslation = {
|
|||||||
description: 'Créez une table pour commencer',
|
description: 'Créez une table pour commencer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Relations',
|
refs: 'Refs',
|
||||||
filter: 'Filtrer',
|
filter: 'Filtrer',
|
||||||
add_relationship: 'Ajouter une Relation',
|
|
||||||
collapse: 'Réduire Tout',
|
collapse: 'Réduire Tout',
|
||||||
|
add_relationship: 'Ajouter une Relation',
|
||||||
|
relationships: 'Relations',
|
||||||
|
dependencies: 'Dépendances',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Relation',
|
||||||
primary: 'Table Principale',
|
primary: 'Table Principale',
|
||||||
foreign: 'Table Référencée',
|
foreign: 'Table Référencée',
|
||||||
cardinality: 'Cardinalité',
|
cardinality: 'Cardinalité',
|
||||||
@@ -176,16 +194,8 @@ export const fr: LanguageTranslation = {
|
|||||||
delete_relationship: 'Supprimer',
|
delete_relationship: 'Supprimer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'Aucune relation',
|
|
||||||
description: 'Créez une relation pour connecter les tables',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Dépendances',
|
|
||||||
filter: 'Filtrer',
|
|
||||||
collapse: 'Réduire Tout',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Dépendance',
|
||||||
table: 'Table',
|
table: 'Table',
|
||||||
dependent_table: 'Vue Dépendante',
|
dependent_table: 'Vue Dépendante',
|
||||||
delete_dependency: 'Supprimer',
|
delete_dependency: 'Supprimer',
|
||||||
@@ -195,8 +205,8 @@ export const fr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Aucune dépendance',
|
title: 'Aucune relation',
|
||||||
description: 'Créez une vue pour commencer',
|
description: 'Créez une relation pour commencer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -220,6 +230,35 @@ export const fr: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuels',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Notes',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filtrer',
|
||||||
|
add_note: 'Ajouter une Note',
|
||||||
|
no_results: 'Aucune note trouvée',
|
||||||
|
clear: 'Effacer le Filtre',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Pas de Notes',
|
||||||
|
description:
|
||||||
|
'Créez une note pour ajouter des annotations de texte sur le canevas',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Note vide',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Actions de Note',
|
||||||
|
edit_content: 'Modifier le Contenu',
|
||||||
|
delete_note: 'Supprimer la Note',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -236,12 +275,16 @@ export const fr: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: "Aucune valeur d'énumération définie",
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -255,8 +298,14 @@ export const fr: LanguageTranslation = {
|
|||||||
show_all: 'Afficher Tout',
|
show_all: 'Afficher Tout',
|
||||||
undo: 'Annuler',
|
undo: 'Annuler',
|
||||||
redo: 'Rétablir',
|
redo: 'Rétablir',
|
||||||
reorder_diagram: 'Réorganiser le Diagramme',
|
reorder_diagram: 'Organiser Automatiquement le Diagramme',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Surligner les tables chevauchées',
|
highlight_overlapping_tables: 'Surligner les tables chevauchées',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -287,13 +336,13 @@ export const fr: LanguageTranslation = {
|
|||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
back: 'Retour',
|
back: 'Retour',
|
||||||
import_from_file: "Importer à partir d'un fichier",
|
import_from_file: "Importer à partir d'un fichier",
|
||||||
empty_diagram: 'Diagramme vide',
|
empty_diagram: 'Base de données vide',
|
||||||
continue: 'Continuer',
|
continue: 'Continuer',
|
||||||
import: 'Importer',
|
import: 'Importer',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Ouvrir Diagramme',
|
title: 'Ouvrir Base de Données',
|
||||||
description:
|
description:
|
||||||
'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.',
|
'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
@@ -304,6 +353,12 @@ export const fr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
open: 'Ouvrir',
|
open: 'Ouvrir',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Ouvrir',
|
||||||
|
duplicate: 'Dupliquer',
|
||||||
|
delete: 'Supprimer',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -341,15 +396,6 @@ export const fr: LanguageTranslation = {
|
|||||||
transparent_description: 'Remove background color from image.',
|
transparent_description: 'Remove background color from image.',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Schémas Multiples',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} schémas dans ce diagramme. Actuellement affiché(s) : {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Ne plus afficher',
|
|
||||||
change_schema: 'Changer',
|
|
||||||
none: 'Aucun',
|
|
||||||
},
|
|
||||||
|
|
||||||
new_table_schema_dialog: {
|
new_table_schema_dialog: {
|
||||||
title: 'Sélectionner un Schéma',
|
title: 'Sélectionner un Schéma',
|
||||||
description:
|
description:
|
||||||
@@ -372,6 +418,13 @@ export const fr: LanguageTranslation = {
|
|||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
confirm: 'Modifier',
|
confirm: 'Modifier',
|
||||||
},
|
},
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Créer un Nouveau Schéma',
|
||||||
|
description:
|
||||||
|
"Aucun schéma n'existe encore. Créez votre premier schéma pour organiser vos tables.",
|
||||||
|
create: 'Créer',
|
||||||
|
cancel: 'Annuler',
|
||||||
|
},
|
||||||
|
|
||||||
create_relationship_dialog: {
|
create_relationship_dialog: {
|
||||||
title: 'Créer une Relation',
|
title: 'Créer une Relation',
|
||||||
@@ -454,9 +507,11 @@ export const fr: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Nouvelle Table',
|
new_table: 'Nouvelle Table',
|
||||||
|
new_view: 'Nouvelle Vue',
|
||||||
new_relationship: 'Nouvelle Relation',
|
new_relationship: 'Nouvelle Relation',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Nouvelle Note',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -476,6 +531,9 @@ export const fr: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Langue',
|
change_language: 'Langue',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Activé',
|
||||||
|
off: 'Désactivé',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const gu: LanguageTranslation = {
|
export const gu: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'નવું',
|
||||||
|
browse: 'બ્રાઉજ',
|
||||||
|
tables: 'ટેબલો',
|
||||||
|
refs: 'રેફ્સ',
|
||||||
|
dependencies: 'નિર્ભરતાઓ',
|
||||||
|
custom_types: 'કસ્ટમ ટાઇપ',
|
||||||
|
visuals: 'Visuals',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'ફાઇલ',
|
actions: 'ક્રિયાઓ',
|
||||||
new: 'નવું',
|
new: 'નવું...',
|
||||||
open: 'ખોલો',
|
browse: 'બ્રાઉજ કરો...',
|
||||||
save: 'સાચવો',
|
save: 'સાચવો',
|
||||||
import: 'ડેટાબેસ આયાત કરો',
|
import: 'ડેટાબેસ આયાત કરો',
|
||||||
export_sql: 'SQL નિકાસ કરો',
|
export_sql: 'SQL નિકાસ કરો',
|
||||||
export_as: 'રૂપે નિકાસ કરો',
|
export_as: 'રૂપે નિકાસ કરો',
|
||||||
delete_diagram: 'ડાયાગ્રામ કાઢી નાખો',
|
delete_diagram: 'કાઢી નાખો',
|
||||||
exit: 'બહાર જાઓ',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'ફેરફાર',
|
edit: 'ફેરફાર',
|
||||||
@@ -26,7 +34,10 @@ export const gu: LanguageTranslation = {
|
|||||||
hide_sidebar: 'સાઇડબાર છુપાવો',
|
hide_sidebar: 'સાઇડબાર છુપાવો',
|
||||||
hide_cardinality: 'કાર્ડિનાલિટી છુપાવો',
|
hide_cardinality: 'કાર્ડિનાલિટી છુપાવો',
|
||||||
show_cardinality: 'કાર્ડિનાલિટી બતાવો',
|
show_cardinality: 'કાર્ડિનાલિટી બતાવો',
|
||||||
|
hide_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ છુપાવો',
|
||||||
|
show_field_attributes: 'ફીલ્ડ અટ્રિબ્યુટ્સ બતાવો',
|
||||||
zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો',
|
zoom_on_scroll: 'સ્ક્રોલ પર ઝૂમ કરો',
|
||||||
|
show_views: 'ડેટાબેઝ વ્યૂઝ',
|
||||||
theme: 'થિમ',
|
theme: 'થિમ',
|
||||||
show_dependencies: 'નિર્ભરતાઓ બતાવો',
|
show_dependencies: 'નિર્ભરતાઓ બતાવો',
|
||||||
hide_dependencies: 'નિર્ભરતાઓ છુપાવો',
|
hide_dependencies: 'નિર્ભરતાઓ છુપાવો',
|
||||||
@@ -64,22 +75,13 @@ export const gu: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'ડાયાગ્રામ ફરી વ્યવસ્થિત કરો',
|
title: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
|
||||||
description:
|
description:
|
||||||
'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?',
|
'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?',
|
||||||
reorder: 'ફરી વ્યવસ્થિત કરો',
|
reorder: 'ઑટોમેટિક ગોઠવો',
|
||||||
cancel: 'રદ કરો',
|
cancel: 'રદ કરો',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'કઈંક વધારે સ્કીમા',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} સ્કીમા આ ડાયાગ્રામમાં છે. હાલમાં દર્શાવેલ છે: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'ફરીથી ન બતાવો',
|
|
||||||
change_schema: 'બદલો',
|
|
||||||
none: 'કઈ નહીં',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'નકલ નિષ્ફળ',
|
title: 'નકલ નિષ્ફળ',
|
||||||
@@ -114,14 +116,11 @@ export const gu: LanguageTranslation = {
|
|||||||
copied: 'નકલ થયું!',
|
copied: 'નકલ થયું!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'સ્કીમા:',
|
|
||||||
filter_by_schema: 'સ્કીમા દ્વારા ફિલ્ટર કરો',
|
|
||||||
search_schema: 'સ્કીમા શોધો...',
|
|
||||||
no_schemas_found: 'કોઈ સ્કીમા મળ્યા નથી.',
|
|
||||||
view_all_options: 'બધા વિકલ્પો જુઓ...',
|
view_all_options: 'બધા વિકલ્પો જુઓ...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'ટેબલ્સ',
|
tables: 'ટેબલ્સ',
|
||||||
add_table: 'ટેબલ ઉમેરો',
|
add_table: 'ટેબલ ઉમેરો',
|
||||||
|
add_view: 'વ્યૂ ઉમેરો',
|
||||||
filter: 'ફિલ્ટર',
|
filter: 'ફિલ્ટર',
|
||||||
collapse: 'બધાને સકુચિત કરો',
|
collapse: 'બધાને સકુચિત કરો',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -148,16 +147,23 @@ export const gu: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'ફીલ્ડ લક્ષણો',
|
title: 'ફીલ્ડ લક્ષણો',
|
||||||
unique: 'અદ્વિતીય',
|
unique: 'અદ્વિતીય',
|
||||||
|
auto_increment: 'ઑટો ઇન્ક્રિમેન્ટ',
|
||||||
comments: 'ટિપ્પણીઓ',
|
comments: 'ટિપ્પણીઓ',
|
||||||
no_comments: 'કોઈ ટિપ્પણીઓ નથી',
|
no_comments: 'કોઈ ટિપ્પણીઓ નથી',
|
||||||
delete_field: 'ફીલ્ડ કાઢી નાખો',
|
delete_field: 'ફીલ્ડ કાઢી નાખો',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'ચોકસાઈ',
|
||||||
|
scale: 'માપ',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'ઇન્ડેક્સ લક્ષણો',
|
title: 'ઇન્ડેક્સ લક્ષણો',
|
||||||
name: 'નામ',
|
name: 'નામ',
|
||||||
unique: 'અદ્વિતીય',
|
unique: 'અદ્વિતીય',
|
||||||
|
index_type: 'ઇન્ડેક્સ પ્રકાર',
|
||||||
delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
|
delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -174,14 +180,17 @@ export const gu: LanguageTranslation = {
|
|||||||
description: 'શરૂ કરવા માટે એક ટેબલ બનાવો',
|
description: 'શરૂ કરવા માટે એક ટેબલ બનાવો',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'સંબંધો',
|
refs: 'રેફ્સ',
|
||||||
filter: 'ફિલ્ટર',
|
filter: 'ફિલ્ટર',
|
||||||
add_relationship: 'સંબંધ ઉમેરો',
|
|
||||||
collapse: 'બધાને સકુચિત કરો',
|
collapse: 'બધાને સકુચિત કરો',
|
||||||
|
add_relationship: 'સંબંધ ઉમેરો',
|
||||||
|
relationships: 'સંબંધો',
|
||||||
|
dependencies: 'નિર્ભરતાઓ',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'સંબંધ',
|
||||||
primary: 'પ્રાથમિક ટેબલ',
|
primary: 'પ્રાથમિક ટેબલ',
|
||||||
foreign: 'સંદર્ભ ટેબલ',
|
foreign: 'સંદર્ભિત ટેબલ',
|
||||||
cardinality: 'કાર્ડિનાલિટી',
|
cardinality: 'કાર્ડિનાલિટી',
|
||||||
delete_relationship: 'કાઢી નાખો',
|
delete_relationship: 'કાઢી નાખો',
|
||||||
relationship_actions: {
|
relationship_actions: {
|
||||||
@@ -189,27 +198,19 @@ export const gu: LanguageTranslation = {
|
|||||||
delete_relationship: 'કાઢી નાખો',
|
delete_relationship: 'કાઢી નાખો',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'કોઈ સંબંધો નથી',
|
|
||||||
description: 'ટેબલ્સ કનેક્ટ કરવા માટે એક સંબંધ બનાવો',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'નિર્ભરતાઓ',
|
|
||||||
filter: 'ફિલ્ટર',
|
|
||||||
collapse: 'સિકોડો',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'નિર્ભરતા',
|
||||||
table: 'ટેબલ',
|
table: 'ટેબલ',
|
||||||
dependent_table: 'આધાર રાખેલું ટેબલ',
|
dependent_table: 'નિર્ભરશીલ વ્યૂ',
|
||||||
delete_dependency: 'નિર્ભરતા કાઢી નાખો',
|
delete_dependency: 'કાઢી નાખો',
|
||||||
dependency_actions: {
|
dependency_actions: {
|
||||||
title: 'ક્રિયાઓ',
|
title: 'ક્રિયાઓ',
|
||||||
delete_dependency: 'નિર્ભરતા કાઢી નાખો',
|
delete_dependency: 'કાઢી નાખો',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'કોઈ નિર્ભરતાઓ નથી',
|
title: 'કોઈ સંબંધો નથી',
|
||||||
description: 'આ વિભાગમાં કોઈ નિર્ભરતા ઉપલબ્ધ નથી.',
|
description: 'શરૂ કરવા માટે એક સંબંધ બનાવો',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -233,6 +234,35 @@ export const gu: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuals',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'નોંધો',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'ફિલ્ટર',
|
||||||
|
add_note: 'નોંધ ઉમેરો',
|
||||||
|
no_results: 'કોઈ નોંધો મળી નથી',
|
||||||
|
clear: 'ફિલ્ટર સાફ કરો',
|
||||||
|
empty_state: {
|
||||||
|
title: 'કોઈ નોંધો નથી',
|
||||||
|
description:
|
||||||
|
'કેનવાસ પર ટેક્સ્ટ એનોટેશન ઉમેરવા માટે નોંધ બનાવો',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'ખાલી નોંધ',
|
||||||
|
note_actions: {
|
||||||
|
title: 'નોંધ ક્રિયાઓ',
|
||||||
|
edit_content: 'સામગ્રી સંપાદિત કરો',
|
||||||
|
delete_note: 'નોંધ કાઢી નાખો',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -249,12 +279,16 @@ export const gu: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'કોઈ enum મૂલ્યો વ્યાખ્યાયિત નથી',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -268,8 +302,14 @@ export const gu: LanguageTranslation = {
|
|||||||
show_all: 'બધું બતાવો',
|
show_all: 'બધું બતાવો',
|
||||||
undo: 'અનડુ',
|
undo: 'અનડુ',
|
||||||
redo: 'રીડુ',
|
redo: 'રીડુ',
|
||||||
reorder_diagram: 'ડાયાગ્રામ ફરીથી વ્યવસ્થિત કરો',
|
reorder_diagram: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'ઓવરલેપ કરતો ટેબલ હાઇલાઇટ કરો',
|
highlight_overlapping_tables: 'ઓવરલેપ કરતો ટેબલ હાઇલાઇટ કરો',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -299,13 +339,13 @@ export const gu: LanguageTranslation = {
|
|||||||
cancel: 'રદ કરો',
|
cancel: 'રદ કરો',
|
||||||
back: 'પાછા',
|
back: 'પાછા',
|
||||||
import_from_file: 'ફાઇલમાંથી આયાત કરો',
|
import_from_file: 'ફાઇલમાંથી આયાત કરો',
|
||||||
empty_diagram: 'ખાલી ડાયાગ્રામ',
|
empty_diagram: 'ખાલી ડેટાબેસ',
|
||||||
continue: 'ચાલુ રાખો',
|
continue: 'ચાલુ રાખો',
|
||||||
import: 'આયાત કરો',
|
import: 'આયાત કરો',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'ડાયાગ્રામ ખોલો',
|
title: 'ડેટાબેસ ખોલો',
|
||||||
description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.',
|
description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'નામ',
|
name: 'નામ',
|
||||||
@@ -315,6 +355,12 @@ export const gu: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'રદ કરો',
|
cancel: 'રદ કરો',
|
||||||
open: 'ખોલો',
|
open: 'ખોલો',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'ખોલો',
|
||||||
|
duplicate: 'ડુપ્લિકેટ',
|
||||||
|
delete: 'કાઢી નાખો',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -401,6 +447,14 @@ export const gu: LanguageTranslation = {
|
|||||||
confirm: 'બદલો',
|
confirm: 'બદલો',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'નવું સ્કીમા બનાવો',
|
||||||
|
description:
|
||||||
|
'હજી સુધી કોઈ સ્કીમા અસ્તિત્વમાં નથી. તમારા ટેબલ્સ ને વ્યવસ્થિત કરવા માટે તમારું પહેલું સ્કીમા બનાવો.',
|
||||||
|
create: 'બનાવો',
|
||||||
|
cancel: 'રદ કરો',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'અમને સુધારવામાં મદદ કરો!',
|
title: 'અમને સુધારવામાં મદદ કરો!',
|
||||||
description:
|
description:
|
||||||
@@ -456,9 +510,11 @@ export const gu: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'નવું ટેબલ',
|
new_table: 'નવું ટેબલ',
|
||||||
|
new_view: 'નવું વ્યૂ',
|
||||||
new_relationship: 'નવો સંબંધ',
|
new_relationship: 'નવો સંબંધ',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'નવી નોંધ',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -477,6 +533,9 @@ export const gu: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'ભાષા બદલો',
|
change_language: 'ભાષા બદલો',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'ચાલુ',
|
||||||
|
off: 'બંધ',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const hi: LanguageTranslation = {
|
export const hi: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'नया',
|
||||||
|
browse: 'ब्राउज़',
|
||||||
|
tables: 'टेबल',
|
||||||
|
refs: 'रेफ्स',
|
||||||
|
dependencies: 'निर्भरताएं',
|
||||||
|
custom_types: 'कस्टम टाइप',
|
||||||
|
visuals: 'Visuals',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'फ़ाइल',
|
actions: 'कार्य',
|
||||||
new: 'नया',
|
new: 'नया...',
|
||||||
open: 'खोलें',
|
browse: 'ब्राउज़ करें...',
|
||||||
save: 'सहेजें',
|
save: 'सहेजें',
|
||||||
import: 'डेटाबेस आयात करें',
|
import: 'डेटाबेस आयात करें',
|
||||||
export_sql: 'SQL निर्यात करें',
|
export_sql: 'SQL निर्यात करें',
|
||||||
export_as: 'के रूप में निर्यात करें',
|
export_as: 'के रूप में निर्यात करें',
|
||||||
delete_diagram: 'आरेख हटाएँ',
|
delete_diagram: 'हटाएँ',
|
||||||
exit: 'बाहर जाएँ',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'संपादित करें',
|
edit: 'संपादित करें',
|
||||||
@@ -26,7 +34,10 @@ export const hi: LanguageTranslation = {
|
|||||||
hide_sidebar: 'साइडबार छिपाएँ',
|
hide_sidebar: 'साइडबार छिपाएँ',
|
||||||
hide_cardinality: 'कार्डिनैलिटी छिपाएँ',
|
hide_cardinality: 'कार्डिनैलिटी छिपाएँ',
|
||||||
show_cardinality: 'कार्डिनैलिटी दिखाएँ',
|
show_cardinality: 'कार्डिनैलिटी दिखाएँ',
|
||||||
|
hide_field_attributes: 'फ़ील्ड विशेषताएँ छिपाएँ',
|
||||||
|
show_field_attributes: 'फ़ील्ड विशेषताएँ दिखाएँ',
|
||||||
zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
|
zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
|
||||||
|
show_views: 'डेटाबेस व्यू',
|
||||||
theme: 'थीम',
|
theme: 'थीम',
|
||||||
show_dependencies: 'निर्भरता दिखाएँ',
|
show_dependencies: 'निर्भरता दिखाएँ',
|
||||||
hide_dependencies: 'निर्भरता छिपाएँ',
|
hide_dependencies: 'निर्भरता छिपाएँ',
|
||||||
@@ -63,22 +74,13 @@ export const hi: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'आरेख पुनः व्यवस्थित करें',
|
title: 'आरेख स्वचालित व्यवस्थित करें',
|
||||||
description:
|
description:
|
||||||
'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?',
|
'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?',
|
||||||
reorder: 'पुनः व्यवस्थित करें',
|
reorder: 'स्वचालित व्यवस्थित करें',
|
||||||
cancel: 'रद्द करें',
|
cancel: 'रद्द करें',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'एकाधिक स्कीमा',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} स्कीमा इस आरेख में हैं। वर्तमान में प्रदर्शित: {{formattedSchemas}}।',
|
|
||||||
dont_show_again: 'फिर से न दिखाएँ',
|
|
||||||
change_schema: 'बदलें',
|
|
||||||
none: 'कोई नहीं',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'कॉपी असफल',
|
title: 'कॉपी असफल',
|
||||||
@@ -114,14 +116,11 @@ export const hi: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'स्कीमा:',
|
|
||||||
filter_by_schema: 'स्कीमा द्वारा फ़िल्टर करें',
|
|
||||||
search_schema: 'स्कीमा खोजें...',
|
|
||||||
no_schemas_found: 'कोई स्कीमा नहीं मिला।',
|
|
||||||
view_all_options: 'सभी विकल्प देखें...',
|
view_all_options: 'सभी विकल्प देखें...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'तालिकाएँ',
|
tables: 'तालिकाएँ',
|
||||||
add_table: 'तालिका जोड़ें',
|
add_table: 'तालिका जोड़ें',
|
||||||
|
add_view: 'व्यू जोड़ें',
|
||||||
filter: 'फ़िल्टर',
|
filter: 'फ़िल्टर',
|
||||||
collapse: 'सभी को संक्षिप्त करें',
|
collapse: 'सभी को संक्षिप्त करें',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -147,16 +146,23 @@ export const hi: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'फ़ील्ड विशेषताएँ',
|
title: 'फ़ील्ड विशेषताएँ',
|
||||||
unique: 'अद्वितीय',
|
unique: 'अद्वितीय',
|
||||||
|
auto_increment: 'ऑटो इंक्रीमेंट',
|
||||||
comments: 'टिप्पणियाँ',
|
comments: 'टिप्पणियाँ',
|
||||||
no_comments: 'कोई टिप्पणी नहीं',
|
no_comments: 'कोई टिप्पणी नहीं',
|
||||||
delete_field: 'फ़ील्ड हटाएँ',
|
delete_field: 'फ़ील्ड हटाएँ',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Precision',
|
||||||
|
scale: 'Scale',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'सूचकांक विशेषताएँ',
|
title: 'सूचकांक विशेषताएँ',
|
||||||
name: 'नाम',
|
name: 'नाम',
|
||||||
unique: 'अद्वितीय',
|
unique: 'अद्वितीय',
|
||||||
|
index_type: 'इंडेक्स प्रकार',
|
||||||
delete_index: 'सूचकांक हटाएँ',
|
delete_index: 'सूचकांक हटाएँ',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -173,12 +179,15 @@ export const hi: LanguageTranslation = {
|
|||||||
description: 'शुरू करने के लिए एक तालिका बनाएँ',
|
description: 'शुरू करने के लिए एक तालिका बनाएँ',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'संबंध',
|
refs: 'रेफ्स',
|
||||||
filter: 'फ़िल्टर',
|
filter: 'फ़िल्टर',
|
||||||
add_relationship: 'संबंध जोड़ें',
|
|
||||||
collapse: 'सभी को संक्षिप्त करें',
|
collapse: 'सभी को संक्षिप्त करें',
|
||||||
|
add_relationship: 'संबंध जोड़ें',
|
||||||
|
relationships: 'संबंध',
|
||||||
|
dependencies: 'निर्भरताएँ',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'संबंध',
|
||||||
primary: 'प्राथमिक तालिका',
|
primary: 'प्राथमिक तालिका',
|
||||||
foreign: 'संदर्भित तालिका',
|
foreign: 'संदर्भित तालिका',
|
||||||
cardinality: 'कार्डिनैलिटी',
|
cardinality: 'कार्डिनैलिटी',
|
||||||
@@ -188,28 +197,19 @@ export const hi: LanguageTranslation = {
|
|||||||
delete_relationship: 'हटाएँ',
|
delete_relationship: 'हटाएँ',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'कोई संबंध नहीं',
|
|
||||||
description:
|
|
||||||
'तालिकाओं को कनेक्ट करने के लिए एक संबंध बनाएँ',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'निर्भरताएँ',
|
|
||||||
filter: 'फ़िल्टर',
|
|
||||||
collapse: 'सिकोड़ें',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'निर्भरता',
|
||||||
table: 'तालिका',
|
table: 'तालिका',
|
||||||
dependent_table: 'आश्रित तालिका',
|
dependent_table: 'आश्रित दृश्य',
|
||||||
delete_dependency: 'निर्भरता हटाएँ',
|
delete_dependency: 'हटाएँ',
|
||||||
dependency_actions: {
|
dependency_actions: {
|
||||||
title: 'कार्रवाइयाँ',
|
title: 'क्रियाएँ',
|
||||||
delete_dependency: 'निर्भरता हटाएँ',
|
delete_dependency: 'हटाएँ',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'कोई निर्भरता नहीं',
|
title: 'कोई संबंध नहीं',
|
||||||
description: 'इस अनुभाग में कोई निर्भरता उपलब्ध नहीं है।',
|
description: 'शुरू करने के लिए एक संबंध बनाएँ',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -233,6 +233,35 @@ export const hi: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuals',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'नोट्स',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'फ़िल्टर',
|
||||||
|
add_note: 'नोट जोड़ें',
|
||||||
|
no_results: 'कोई नोट नहीं मिला',
|
||||||
|
clear: 'फ़िल्टर साफ़ करें',
|
||||||
|
empty_state: {
|
||||||
|
title: 'कोई नोट नहीं',
|
||||||
|
description:
|
||||||
|
'कैनवास पर टेक्स्ट एनोटेशन जोड़ने के लिए एक नोट बनाएं',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'खाली नोट',
|
||||||
|
note_actions: {
|
||||||
|
title: 'नोट क्रियाएं',
|
||||||
|
edit_content: 'सामग्री संपादित करें',
|
||||||
|
delete_note: 'नोट हटाएं',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -249,12 +278,16 @@ export const hi: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'कोई enum मान परिभाषित नहीं',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -268,8 +301,14 @@ export const hi: LanguageTranslation = {
|
|||||||
show_all: 'सभी दिखाएँ',
|
show_all: 'सभी दिखाएँ',
|
||||||
undo: 'पूर्ववत करें',
|
undo: 'पूर्ववत करें',
|
||||||
redo: 'पुनः करें',
|
redo: 'पुनः करें',
|
||||||
reorder_diagram: 'आरेख पुनः व्यवस्थित करें',
|
reorder_diagram: 'आरेख स्वचालित व्यवस्थित करें',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'ओवरलैपिंग तालिकाओं को हाइलाइट करें',
|
highlight_overlapping_tables: 'ओवरलैपिंग तालिकाओं को हाइलाइट करें',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -302,13 +341,13 @@ export const hi: LanguageTranslation = {
|
|||||||
back: 'वापस',
|
back: 'वापस',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'खाली आरेख',
|
empty_diagram: 'खाली डेटाबेस',
|
||||||
continue: 'जारी रखें',
|
continue: 'जारी रखें',
|
||||||
import: 'आयात करें',
|
import: 'आयात करें',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'आरेख खोलें',
|
title: 'डेटाबेस खोलें',
|
||||||
description: 'नीचे दी गई सूची से एक आरेख चुनें।',
|
description: 'नीचे दी गई सूची से एक आरेख चुनें।',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'नाम',
|
name: 'नाम',
|
||||||
@@ -318,6 +357,12 @@ export const hi: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'रद्द करें',
|
cancel: 'रद्द करें',
|
||||||
open: 'खोलें',
|
open: 'खोलें',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'खोलें',
|
||||||
|
duplicate: 'डुप्लिकेट',
|
||||||
|
delete: 'हटाएं',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -404,6 +449,14 @@ export const hi: LanguageTranslation = {
|
|||||||
confirm: 'बदलें',
|
confirm: 'बदलें',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'नया स्कीमा बनाएं',
|
||||||
|
description:
|
||||||
|
'अभी तक कोई स्कीमा मौजूद नहीं है। अपनी तालिकाओं को व्यवस्थित करने के लिए अपना पहला स्कीमा बनाएं।',
|
||||||
|
create: 'बनाएं',
|
||||||
|
cancel: 'रद्द करें',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'हमें सुधारने में मदद करें!',
|
title: 'हमें सुधारने में मदद करें!',
|
||||||
description:
|
description:
|
||||||
@@ -459,9 +512,11 @@ export const hi: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'नई तालिका',
|
new_table: 'नई तालिका',
|
||||||
|
new_view: 'नया व्यू',
|
||||||
new_relationship: 'नया संबंध',
|
new_relationship: 'नया संबंध',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'नया नोट',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -481,6 +536,9 @@ export const hi: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'भाषा बदलें',
|
change_language: 'भाषा बदलें',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'चालू',
|
||||||
|
off: 'बंद',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
538
src/i18n/locales/hr.ts
Normal file
538
src/i18n/locales/hr.ts
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||||
|
|
||||||
|
export const hr: LanguageTranslation = {
|
||||||
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Novi',
|
||||||
|
browse: 'Pregledaj',
|
||||||
|
tables: 'Tablice',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Ovisnosti',
|
||||||
|
custom_types: 'Prilagođeni Tipovi',
|
||||||
|
visuals: 'Vizuali',
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Vizuali',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Područja',
|
||||||
|
notes: 'Bilješke',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filtriraj',
|
||||||
|
add_note: 'Dodaj Bilješku',
|
||||||
|
no_results: 'Nije pronađena nijedna bilješka',
|
||||||
|
clear: 'Očisti Filter',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Nema Bilješki',
|
||||||
|
description:
|
||||||
|
'Kreirajte bilješku za dodavanje tekstualnih napomena na platnu',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Prazna bilješka',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Akcije Bilješke',
|
||||||
|
edit_content: 'Uredi Sadržaj',
|
||||||
|
delete_note: 'Obriši Bilješku',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
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: 'Prazna baza podataka',
|
||||||
|
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',
|
||||||
|
new_note: 'Nova Bilješka',
|
||||||
|
},
|
||||||
|
|
||||||
|
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',
|
||||||
|
};
|
||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const id_ID: LanguageTranslation = {
|
export const id_ID: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Baru',
|
||||||
|
browse: 'Jelajahi',
|
||||||
|
tables: 'Tabel',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Ketergantungan',
|
||||||
|
custom_types: 'Tipe Kustom',
|
||||||
|
visuals: 'Visual',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Berkas',
|
actions: 'Aksi',
|
||||||
new: 'Buat Baru',
|
new: 'Baru...',
|
||||||
open: 'Buka',
|
browse: 'Jelajahi...',
|
||||||
save: 'Simpan',
|
save: 'Simpan',
|
||||||
import: 'Impor Database',
|
import: 'Impor Database',
|
||||||
export_sql: 'Ekspor SQL',
|
export_sql: 'Ekspor SQL',
|
||||||
export_as: 'Ekspor Sebagai',
|
export_as: 'Ekspor Sebagai',
|
||||||
delete_diagram: 'Hapus Diagram',
|
delete_diagram: 'Hapus',
|
||||||
exit: 'Keluar',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Ubah',
|
edit: 'Ubah',
|
||||||
@@ -26,7 +34,10 @@ export const id_ID: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Sembunyikan Sidebar',
|
hide_sidebar: 'Sembunyikan Sidebar',
|
||||||
hide_cardinality: 'Sembunyikan Kardinalitas',
|
hide_cardinality: 'Sembunyikan Kardinalitas',
|
||||||
show_cardinality: 'Tampilkan Kardinalitas',
|
show_cardinality: 'Tampilkan Kardinalitas',
|
||||||
|
hide_field_attributes: 'Sembunyikan Atribut Kolom',
|
||||||
|
show_field_attributes: 'Tampilkan Atribut Kolom',
|
||||||
zoom_on_scroll: 'Perbesar saat Scroll',
|
zoom_on_scroll: 'Perbesar saat Scroll',
|
||||||
|
show_views: 'Tampilan Database',
|
||||||
theme: 'Tema',
|
theme: 'Tema',
|
||||||
show_dependencies: 'Tampilkan Dependensi',
|
show_dependencies: 'Tampilkan Dependensi',
|
||||||
hide_dependencies: 'Sembunyikan Dependensi',
|
hide_dependencies: 'Sembunyikan Dependensi',
|
||||||
@@ -63,22 +74,13 @@ export const id_ID: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Atur Ulang Diagram',
|
title: 'Atur Otomatis Diagram',
|
||||||
description:
|
description:
|
||||||
'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?',
|
'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?',
|
||||||
reorder: 'Atur Ulang',
|
reorder: 'Atur Otomatis',
|
||||||
cancel: 'Batal',
|
cancel: 'Batal',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Schema Lebih dari satu',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} schema di diagram ini. Sedang ditampilkan: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Jangan tampilkan lagi',
|
|
||||||
change_schema: 'Ubah',
|
|
||||||
none: 'Tidak ada',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Gagal menyalin',
|
title: 'Gagal menyalin',
|
||||||
@@ -113,14 +115,11 @@ export const id_ID: LanguageTranslation = {
|
|||||||
copied: 'Tersalin!',
|
copied: 'Tersalin!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Skema:',
|
|
||||||
filter_by_schema: 'Saring berdasarkan skema',
|
|
||||||
search_schema: 'Cari skema...',
|
|
||||||
no_schemas_found: 'Tidak ada skema yang ditemukan.',
|
|
||||||
view_all_options: 'Tampilkan Semua Pilihan...',
|
view_all_options: 'Tampilkan Semua Pilihan...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tabel',
|
tables: 'Tabel',
|
||||||
add_table: 'Tambah Tabel',
|
add_table: 'Tambah Tabel',
|
||||||
|
add_view: 'Tambah Tampilan',
|
||||||
filter: 'Saring',
|
filter: 'Saring',
|
||||||
collapse: 'Lipat Semua',
|
collapse: 'Lipat Semua',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -146,16 +145,23 @@ export const id_ID: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Atribut Kolom',
|
title: 'Atribut Kolom',
|
||||||
unique: 'Unik',
|
unique: 'Unik',
|
||||||
|
auto_increment: 'Kenaikan Otomatis',
|
||||||
comments: 'Komentar',
|
comments: 'Komentar',
|
||||||
no_comments: 'Tidak ada komentar',
|
no_comments: 'Tidak ada komentar',
|
||||||
delete_field: 'Hapus Kolom',
|
delete_field: 'Hapus Kolom',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Presisi',
|
||||||
|
scale: 'Skala',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Atribut Indeks',
|
title: 'Atribut Indeks',
|
||||||
name: 'Nama',
|
name: 'Nama',
|
||||||
unique: 'Unik',
|
unique: 'Unik',
|
||||||
|
index_type: 'Tipe Indeks',
|
||||||
delete_index: 'Hapus Indeks',
|
delete_index: 'Hapus Indeks',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -172,12 +178,15 @@ export const id_ID: LanguageTranslation = {
|
|||||||
description: 'Buat tabel untuk memulai',
|
description: 'Buat tabel untuk memulai',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Hubungan',
|
refs: 'Refs',
|
||||||
filter: 'Saring',
|
filter: 'Saring',
|
||||||
add_relationship: 'Tambah Hubungan',
|
|
||||||
collapse: 'Lipat Semua',
|
collapse: 'Lipat Semua',
|
||||||
|
add_relationship: 'Tambah Hubungan',
|
||||||
|
relationships: 'Hubungan',
|
||||||
|
dependencies: 'Dependensi',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Hubungan',
|
||||||
primary: 'Tabel Primer',
|
primary: 'Tabel Primer',
|
||||||
foreign: 'Tabel Referensi',
|
foreign: 'Tabel Referensi',
|
||||||
cardinality: 'Kardinalitas',
|
cardinality: 'Kardinalitas',
|
||||||
@@ -187,16 +196,8 @@ export const id_ID: LanguageTranslation = {
|
|||||||
delete_relationship: 'Hapus',
|
delete_relationship: 'Hapus',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'Tidak ada hubungan',
|
|
||||||
description: 'Buat hubungan untuk menghubungkan tabel',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Dependensi',
|
|
||||||
filter: 'Saring',
|
|
||||||
collapse: 'Lipat Semua',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Dependensi',
|
||||||
table: 'Tabel',
|
table: 'Tabel',
|
||||||
dependent_table: 'Tampilan Dependen',
|
dependent_table: 'Tampilan Dependen',
|
||||||
delete_dependency: 'Hapus',
|
delete_dependency: 'Hapus',
|
||||||
@@ -206,8 +207,8 @@ export const id_ID: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Tidak ada dependensi',
|
title: 'Tidak ada hubungan',
|
||||||
description: 'Buat tampilan untuk memulai',
|
description: 'Buat hubungan untuk memulai',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -231,6 +232,35 @@ export const id_ID: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visual',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Catatan',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filter',
|
||||||
|
add_note: 'Tambah Catatan',
|
||||||
|
no_results: 'Tidak ada catatan ditemukan',
|
||||||
|
clear: 'Hapus Filter',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Tidak Ada Catatan',
|
||||||
|
description:
|
||||||
|
'Buat catatan untuk menambahkan anotasi teks di kanvas',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Catatan kosong',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Aksi Catatan',
|
||||||
|
edit_content: 'Edit Konten',
|
||||||
|
delete_note: 'Hapus Catatan',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -247,12 +277,16 @@ export const id_ID: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'Tidak ada nilai enum yang ditentukan',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -266,8 +300,14 @@ export const id_ID: LanguageTranslation = {
|
|||||||
show_all: 'Tampilkan Semua',
|
show_all: 'Tampilkan Semua',
|
||||||
undo: 'Undo',
|
undo: 'Undo',
|
||||||
redo: 'Redo',
|
redo: 'Redo',
|
||||||
reorder_diagram: 'Atur Ulang Diagram',
|
reorder_diagram: 'Atur Otomatis Diagram',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Sorot Tabel yang Tumpang Tindih',
|
highlight_overlapping_tables: 'Sorot Tabel yang Tumpang Tindih',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -298,13 +338,13 @@ export const id_ID: LanguageTranslation = {
|
|||||||
cancel: 'Batal',
|
cancel: 'Batal',
|
||||||
import_from_file: 'Impor dari file',
|
import_from_file: 'Impor dari file',
|
||||||
back: 'Kembali',
|
back: 'Kembali',
|
||||||
empty_diagram: 'Diagram Kosong',
|
empty_diagram: 'Database Kosong',
|
||||||
continue: 'Lanjutkan',
|
continue: 'Lanjutkan',
|
||||||
import: 'Impor',
|
import: 'Impor',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Buka Diagram',
|
title: 'Buka Database',
|
||||||
description: 'Pilih diagram untuk dibuka dari daftar di bawah.',
|
description: 'Pilih diagram untuk dibuka dari daftar di bawah.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
@@ -314,6 +354,12 @@ export const id_ID: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Batal',
|
cancel: 'Batal',
|
||||||
open: 'Buka',
|
open: 'Buka',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Buka',
|
||||||
|
duplicate: 'Duplikat',
|
||||||
|
delete: 'Hapus',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -399,6 +445,14 @@ export const id_ID: LanguageTranslation = {
|
|||||||
confirm: 'Ubah',
|
confirm: 'Ubah',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Buat Skema Baru',
|
||||||
|
description:
|
||||||
|
'Belum ada skema yang tersedia. Buat skema pertama Anda untuk mengatur tabel-tabel Anda.',
|
||||||
|
create: 'Buat',
|
||||||
|
cancel: 'Batal',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Bantu kami meningkatkan!',
|
title: 'Bantu kami meningkatkan!',
|
||||||
description:
|
description:
|
||||||
@@ -455,9 +509,11 @@ export const id_ID: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Tabel Baru',
|
new_table: 'Tabel Baru',
|
||||||
|
new_view: 'Tampilan Baru',
|
||||||
new_relationship: 'Hubungan Baru',
|
new_relationship: 'Hubungan Baru',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Catatan Baru',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -476,6 +532,9 @@ export const id_ID: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Bahasa',
|
change_language: 'Bahasa',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Aktif',
|
||||||
|
off: 'Nonaktif',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const ja: LanguageTranslation = {
|
export const ja: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: '新規',
|
||||||
|
browse: '参照',
|
||||||
|
tables: 'テーブル',
|
||||||
|
refs: '参照',
|
||||||
|
dependencies: '依存関係',
|
||||||
|
custom_types: 'カスタムタイプ',
|
||||||
|
visuals: 'ビジュアル',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'ファイル',
|
actions: 'アクション',
|
||||||
new: '新規',
|
new: '新規...',
|
||||||
open: '開く',
|
browse: '参照...',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
import: 'データベースをインポート',
|
import: 'データベースをインポート',
|
||||||
export_sql: 'SQLをエクスポート',
|
export_sql: 'SQLをエクスポート',
|
||||||
export_as: '形式を指定してエクスポート',
|
export_as: '形式を指定してエクスポート',
|
||||||
delete_diagram: 'ダイアグラムを削除',
|
delete_diagram: '削除',
|
||||||
exit: '終了',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: '編集',
|
edit: '編集',
|
||||||
@@ -26,7 +34,10 @@ export const ja: LanguageTranslation = {
|
|||||||
hide_sidebar: 'サイドバーを非表示',
|
hide_sidebar: 'サイドバーを非表示',
|
||||||
hide_cardinality: 'カーディナリティを非表示',
|
hide_cardinality: 'カーディナリティを非表示',
|
||||||
show_cardinality: 'カーディナリティを表示',
|
show_cardinality: 'カーディナリティを表示',
|
||||||
|
hide_field_attributes: 'フィールド属性を非表示',
|
||||||
|
show_field_attributes: 'フィールド属性を表示',
|
||||||
zoom_on_scroll: 'スクロールでズーム',
|
zoom_on_scroll: 'スクロールでズーム',
|
||||||
|
show_views: 'データベースビュー',
|
||||||
theme: 'テーマ',
|
theme: 'テーマ',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
show_dependencies: 'Show Dependencies',
|
show_dependencies: 'Show Dependencies',
|
||||||
@@ -65,22 +76,13 @@ export const ja: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'ダイアグラムを並べ替え',
|
title: 'ダイアグラムを自動配置',
|
||||||
description:
|
description:
|
||||||
'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?',
|
'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?',
|
||||||
reorder: '並べ替え',
|
reorder: '自動配置',
|
||||||
cancel: 'キャンセル',
|
cancel: 'キャンセル',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '複数のスキーマ',
|
|
||||||
description:
|
|
||||||
'このダイアグラムには{{schemasCount}}個のスキーマがあります。現在表示中: {{formattedSchemas}}。',
|
|
||||||
dont_show_again: '再表示しない',
|
|
||||||
change_schema: '変更',
|
|
||||||
none: 'なし',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'コピー失敗',
|
title: 'コピー失敗',
|
||||||
@@ -117,14 +119,11 @@ export const ja: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'スキーマ:',
|
|
||||||
filter_by_schema: 'スキーマでフィルタ',
|
|
||||||
search_schema: 'スキーマを検索...',
|
|
||||||
no_schemas_found: 'スキーマが見つかりません。',
|
|
||||||
view_all_options: 'すべてのオプションを表示...',
|
view_all_options: 'すべてのオプションを表示...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'テーブル',
|
tables: 'テーブル',
|
||||||
add_table: 'テーブルを追加',
|
add_table: 'テーブルを追加',
|
||||||
|
add_view: 'ビューを追加',
|
||||||
filter: 'フィルタ',
|
filter: 'フィルタ',
|
||||||
collapse: 'すべて折りたたむ',
|
collapse: 'すべて折りたたむ',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -150,16 +149,23 @@ export const ja: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'フィールド属性',
|
title: 'フィールド属性',
|
||||||
unique: 'ユニーク',
|
unique: 'ユニーク',
|
||||||
|
auto_increment: 'オートインクリメント',
|
||||||
comments: 'コメント',
|
comments: 'コメント',
|
||||||
no_comments: 'コメントがありません',
|
no_comments: 'コメントがありません',
|
||||||
delete_field: 'フィールドを削除',
|
delete_field: 'フィールドを削除',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: '精度',
|
||||||
|
scale: '小数点以下桁数',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'インデックス属性',
|
title: 'インデックス属性',
|
||||||
name: '名前',
|
name: '名前',
|
||||||
unique: 'ユニーク',
|
unique: 'ユニーク',
|
||||||
|
index_type: 'インデックスタイプ',
|
||||||
delete_index: 'インデックスを削除',
|
delete_index: 'インデックスを削除',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -176,12 +182,15 @@ export const ja: LanguageTranslation = {
|
|||||||
description: 'テーブルを作成して開始してください',
|
description: 'テーブルを作成して開始してください',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'リレーションシップ',
|
refs: '参照',
|
||||||
filter: 'フィルタ',
|
filter: 'フィルタ',
|
||||||
add_relationship: 'リレーションシップを追加',
|
|
||||||
collapse: 'すべて折りたたむ',
|
collapse: 'すべて折りたたむ',
|
||||||
|
add_relationship: 'リレーションシップを追加',
|
||||||
|
relationships: 'リレーションシップ',
|
||||||
|
dependencies: '依存関係',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'リレーションシップ',
|
||||||
primary: '主テーブル',
|
primary: '主テーブル',
|
||||||
foreign: '参照テーブル',
|
foreign: '参照テーブル',
|
||||||
cardinality: 'カーディナリティ',
|
cardinality: 'カーディナリティ',
|
||||||
@@ -191,29 +200,20 @@ export const ja: LanguageTranslation = {
|
|||||||
delete_relationship: '削除',
|
delete_relationship: '削除',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'リレーションシップがありません',
|
|
||||||
description:
|
|
||||||
'テーブルを接続するためにリレーションシップを作成してください',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// TODO: Translate
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Dependencies',
|
|
||||||
filter: 'Filter',
|
|
||||||
collapse: 'Collapse All',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
table: 'Table',
|
dependency: '依存関係',
|
||||||
dependent_table: 'Dependent View',
|
table: 'テーブル',
|
||||||
delete_dependency: 'Delete',
|
dependent_table: '依存ビュー',
|
||||||
|
delete_dependency: '削除',
|
||||||
dependency_actions: {
|
dependency_actions: {
|
||||||
title: 'Actions',
|
title: '操作',
|
||||||
delete_dependency: 'Delete',
|
delete_dependency: '削除',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'No dependencies',
|
title: 'リレーションシップがありません',
|
||||||
description: 'Create a view to get started',
|
description:
|
||||||
|
'開始するためにリレーションシップを作成してください',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -237,6 +237,35 @@ export const ja: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'ビジュアル',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'ノート',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'フィルター',
|
||||||
|
add_note: 'ノートを追加',
|
||||||
|
no_results: 'ノートが見つかりません',
|
||||||
|
clear: 'フィルターをクリア',
|
||||||
|
empty_state: {
|
||||||
|
title: 'ノートがありません',
|
||||||
|
description:
|
||||||
|
'キャンバス上にテキスト注釈を追加するためのノートを作成',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: '空のノート',
|
||||||
|
note_actions: {
|
||||||
|
title: 'ノートアクション',
|
||||||
|
edit_content: 'コンテンツを編集',
|
||||||
|
delete_note: 'ノートを削除',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -253,12 +282,16 @@ export const ja: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: '列挙値が定義されていません',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -272,9 +305,13 @@ export const ja: LanguageTranslation = {
|
|||||||
show_all: 'すべて表示',
|
show_all: 'すべて表示',
|
||||||
undo: '元に戻す',
|
undo: '元に戻す',
|
||||||
redo: 'やり直し',
|
redo: 'やり直し',
|
||||||
reorder_diagram: 'ダイアグラムを並べ替え',
|
reorder_diagram: 'ダイアグラムを自動配置',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
highlight_overlapping_tables: 'Highlight Overlapping Tables',
|
highlight_overlapping_tables: 'Highlight Overlapping Tables',
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear', // TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -306,13 +343,13 @@ export const ja: LanguageTranslation = {
|
|||||||
back: '戻る',
|
back: '戻る',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: '空のダイアグラム',
|
empty_diagram: '空のデータベース',
|
||||||
continue: '続行',
|
continue: '続行',
|
||||||
import: 'インポート',
|
import: 'インポート',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'ダイアグラムを開く',
|
title: 'データベースを開く',
|
||||||
description: '以下のリストからダイアグラムを選択してください。',
|
description: '以下のリストからダイアグラムを選択してください。',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: '名前',
|
name: '名前',
|
||||||
@@ -322,6 +359,12 @@ export const ja: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'キャンセル',
|
cancel: 'キャンセル',
|
||||||
open: '開く',
|
open: '開く',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: '開く',
|
||||||
|
duplicate: '複製',
|
||||||
|
delete: '削除',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -408,6 +451,14 @@ export const ja: LanguageTranslation = {
|
|||||||
confirm: '変更',
|
confirm: '変更',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: '新しいスキーマを作成',
|
||||||
|
description:
|
||||||
|
'スキーマがまだ存在しません。テーブルを整理するために最初のスキーマを作成してください。',
|
||||||
|
create: '作成',
|
||||||
|
cancel: 'キャンセル',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: '改善をサポートしてください!',
|
title: '改善をサポートしてください!',
|
||||||
description:
|
description:
|
||||||
@@ -463,9 +514,11 @@ export const ja: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '新しいテーブル',
|
new_table: '新しいテーブル',
|
||||||
|
new_view: '新しいビュー',
|
||||||
new_relationship: '新しいリレーションシップ',
|
new_relationship: '新しいリレーションシップ',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: '新しいメモ',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -485,6 +538,9 @@ export const ja: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: '言語',
|
change_language: '言語',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'オン',
|
||||||
|
off: 'オフ',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const ko_KR: LanguageTranslation = {
|
export const ko_KR: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: '새로 만들기',
|
||||||
|
browse: '찾아보기',
|
||||||
|
tables: '테이블',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: '종속성',
|
||||||
|
custom_types: '사용자 지정 타입',
|
||||||
|
visuals: '시각화',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: '파일',
|
actions: '작업',
|
||||||
new: '새 다이어그램',
|
new: '새로 만들기...',
|
||||||
open: '열기',
|
browse: '찾아보기...',
|
||||||
save: '저장',
|
save: '저장',
|
||||||
import: '데이터베이스 가져오기',
|
import: '데이터베이스 가져오기',
|
||||||
export_sql: 'SQL로 저장',
|
export_sql: 'SQL로 저장',
|
||||||
export_as: '다른 형식으로 저장',
|
export_as: '다른 형식으로 저장',
|
||||||
delete_diagram: '다이어그램 삭제',
|
delete_diagram: '삭제',
|
||||||
exit: '종료',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: '편집',
|
edit: '편집',
|
||||||
@@ -26,7 +34,10 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
hide_sidebar: '사이드바 숨기기',
|
hide_sidebar: '사이드바 숨기기',
|
||||||
hide_cardinality: '카디널리티 숨기기',
|
hide_cardinality: '카디널리티 숨기기',
|
||||||
show_cardinality: '카디널리티 보이기',
|
show_cardinality: '카디널리티 보이기',
|
||||||
|
hide_field_attributes: '필드 속성 숨기기',
|
||||||
|
show_field_attributes: '필드 속성 보이기',
|
||||||
zoom_on_scroll: '스크롤 시 확대',
|
zoom_on_scroll: '스크롤 시 확대',
|
||||||
|
show_views: '데이터베이스 뷰',
|
||||||
theme: '테마',
|
theme: '테마',
|
||||||
show_dependencies: '종속성 보이기',
|
show_dependencies: '종속성 보이기',
|
||||||
hide_dependencies: '종속성 숨기기',
|
hide_dependencies: '종속성 숨기기',
|
||||||
@@ -63,22 +74,13 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: '다이어그램 재정렬',
|
title: '다이어그램 자동 정렬',
|
||||||
description:
|
description:
|
||||||
'이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?',
|
'이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?',
|
||||||
reorder: '재정렬',
|
reorder: '자동 정렬',
|
||||||
cancel: '취소',
|
cancel: '취소',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '다중 스키마',
|
|
||||||
description:
|
|
||||||
'현재 다이어그램에 {{schemasCount}}개의 스키마가 있습니다. Currently displaying: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: '다시 보여주지 마세요',
|
|
||||||
change_schema: '변경',
|
|
||||||
none: '없음',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: '복사 실패',
|
title: '복사 실패',
|
||||||
@@ -113,14 +115,11 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
copied: '복사됨!',
|
copied: '복사됨!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: '스키마:',
|
|
||||||
filter_by_schema: '스키마로 필터링',
|
|
||||||
search_schema: '스키마 검색...',
|
|
||||||
no_schemas_found: '스키마를 찾을 수 없습니다.',
|
|
||||||
view_all_options: '전체 옵션 보기...',
|
view_all_options: '전체 옵션 보기...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: '테이블',
|
tables: '테이블',
|
||||||
add_table: '테이블 추가',
|
add_table: '테이블 추가',
|
||||||
|
add_view: '뷰 추가',
|
||||||
filter: '필터',
|
filter: '필터',
|
||||||
collapse: '모두 접기',
|
collapse: '모두 접기',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -146,16 +145,23 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: '필드 속성',
|
title: '필드 속성',
|
||||||
unique: '유니크 여부',
|
unique: '유니크 여부',
|
||||||
|
auto_increment: '자동 증가',
|
||||||
comments: '주석',
|
comments: '주석',
|
||||||
no_comments: '주석 없음',
|
no_comments: '주석 없음',
|
||||||
delete_field: '필드 삭제',
|
delete_field: '필드 삭제',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: '정밀도',
|
||||||
|
scale: '소수점 자릿수',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: '인덱스 속성',
|
title: '인덱스 속성',
|
||||||
name: '인덱스 명',
|
name: '인덱스 명',
|
||||||
unique: '유니크 여부',
|
unique: '유니크 여부',
|
||||||
|
index_type: '인덱스 타입',
|
||||||
delete_index: '인덱스 삭제',
|
delete_index: '인덱스 삭제',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -172,12 +178,15 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
description: '테이블을 만들어 시작하세요.',
|
description: '테이블을 만들어 시작하세요.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: '연관 관계',
|
refs: 'Refs',
|
||||||
filter: '필터',
|
filter: '필터',
|
||||||
add_relationship: '연관 관계 추가',
|
|
||||||
collapse: '모두 접기',
|
collapse: '모두 접기',
|
||||||
|
add_relationship: '연관 관계 추가',
|
||||||
|
relationships: '연관 관계',
|
||||||
|
dependencies: '종속성',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: '연관 관계',
|
||||||
primary: '주 테이블',
|
primary: '주 테이블',
|
||||||
foreign: '참조 테이블',
|
foreign: '참조 테이블',
|
||||||
cardinality: '카디널리티',
|
cardinality: '카디널리티',
|
||||||
@@ -187,16 +196,8 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
delete_relationship: '연관 관계 삭제',
|
delete_relationship: '연관 관계 삭제',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: '연관 관계',
|
|
||||||
description: '테이블 연결을 위해 연관 관계를 생성하세요',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: '종속성',
|
|
||||||
filter: '필터',
|
|
||||||
collapse: '모두 접기',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: '종속성',
|
||||||
table: '테이블',
|
table: '테이블',
|
||||||
dependent_table: '뷰 테이블',
|
dependent_table: '뷰 테이블',
|
||||||
delete_dependency: '삭제',
|
delete_dependency: '삭제',
|
||||||
@@ -206,8 +207,8 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: '뷰 테이블 없음',
|
title: '연관 관계 없음',
|
||||||
description: '뷰 테이블을 만들어 시작하세요.',
|
description: '연관 관계를 만들어 시작하세요.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -231,6 +232,35 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: '시각화',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: '메모',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: '필터',
|
||||||
|
add_note: '메모 추가',
|
||||||
|
no_results: '메모를 찾을 수 없습니다',
|
||||||
|
clear: '필터 지우기',
|
||||||
|
empty_state: {
|
||||||
|
title: '메모 없음',
|
||||||
|
description:
|
||||||
|
'캔버스에 텍스트 주석을 추가하려면 메모를 만드세요',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: '빈 메모',
|
||||||
|
note_actions: {
|
||||||
|
title: '메모 작업',
|
||||||
|
edit_content: '내용 편집',
|
||||||
|
delete_note: '메모 삭제',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -247,12 +277,16 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: '정의된 열거형 값이 없습니다',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -266,8 +300,14 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
show_all: '전체 저장',
|
show_all: '전체 저장',
|
||||||
undo: '실행 취소',
|
undo: '실행 취소',
|
||||||
redo: '다시 실행',
|
redo: '다시 실행',
|
||||||
reorder_diagram: '다이어그램 재정렬',
|
reorder_diagram: '다이어그램 자동 정렬',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: '겹치는 테이블 강조 표시',
|
highlight_overlapping_tables: '겹치는 테이블 강조 표시',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -298,13 +338,13 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
cancel: '취소',
|
cancel: '취소',
|
||||||
back: '뒤로가기',
|
back: '뒤로가기',
|
||||||
import_from_file: '파일에서 가져오기',
|
import_from_file: '파일에서 가져오기',
|
||||||
empty_diagram: '빈 다이어그램으로 시작',
|
empty_diagram: '빈 데이터베이스',
|
||||||
continue: '계속',
|
continue: '계속',
|
||||||
import: '가져오기',
|
import: '가져오기',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: '다이어그램 열기',
|
title: '데이터베이스 열기',
|
||||||
description: '아래의 목록에서 다이어그램을 선택하세요.',
|
description: '아래의 목록에서 다이어그램을 선택하세요.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: '이름',
|
name: '이름',
|
||||||
@@ -314,6 +354,12 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: '취소',
|
cancel: '취소',
|
||||||
open: '열기',
|
open: '열기',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: '열기',
|
||||||
|
duplicate: '복제',
|
||||||
|
delete: '삭제',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -399,6 +445,14 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
confirm: '변경',
|
confirm: '변경',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: '새 스키마 생성',
|
||||||
|
description:
|
||||||
|
'아직 스키마가 없습니다. 테이블을 정리하기 위해 첫 번째 스키마를 생성하세요.',
|
||||||
|
create: '생성',
|
||||||
|
cancel: '취소',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: '개선할 수 있도록 도와주세요!',
|
title: '개선할 수 있도록 도와주세요!',
|
||||||
description:
|
description:
|
||||||
@@ -452,9 +506,11 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '새 테이블',
|
new_table: '새 테이블',
|
||||||
|
new_view: '새 뷰',
|
||||||
new_relationship: '새 연관관계',
|
new_relationship: '새 연관관계',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: '새 메모',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -473,6 +529,9 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: '언어',
|
change_language: '언어',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: '켜기',
|
||||||
|
off: '끄기',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const mr: LanguageTranslation = {
|
export const mr: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'नवीन',
|
||||||
|
browse: 'ब्राउज',
|
||||||
|
tables: 'टेबल',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'अवलंबने',
|
||||||
|
custom_types: 'कस्टम प्रकार',
|
||||||
|
visuals: 'Visuals',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'फाइल',
|
actions: 'क्रिया',
|
||||||
new: 'नवीन',
|
new: 'नवीन...',
|
||||||
open: 'उघडा',
|
browse: 'ब्राउज करा...',
|
||||||
save: 'जतन करा',
|
save: 'जतन करा',
|
||||||
import: 'डेटाबेस इम्पोर्ट करा',
|
import: 'डेटाबेस इम्पोर्ट करा',
|
||||||
export_sql: 'SQL एक्स्पोर्ट करा',
|
export_sql: 'SQL एक्स्पोर्ट करा',
|
||||||
export_as: 'म्हणून एक्स्पोर्ट करा',
|
export_as: 'म्हणून एक्स्पोर्ट करा',
|
||||||
delete_diagram: 'आरेख हटवा',
|
delete_diagram: 'हटवा',
|
||||||
exit: 'बाहेर पडा',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'संपादन करा',
|
edit: 'संपादन करा',
|
||||||
@@ -26,7 +34,10 @@ export const mr: LanguageTranslation = {
|
|||||||
hide_sidebar: 'साइडबार लपवा',
|
hide_sidebar: 'साइडबार लपवा',
|
||||||
hide_cardinality: 'कार्डिनॅलिटी लपवा',
|
hide_cardinality: 'कार्डिनॅलिटी लपवा',
|
||||||
show_cardinality: 'कार्डिनॅलिटी दाखवा',
|
show_cardinality: 'कार्डिनॅलिटी दाखवा',
|
||||||
|
hide_field_attributes: 'फील्ड गुणधर्म लपवा',
|
||||||
|
show_field_attributes: 'फील्ड गुणधर्म दाखवा',
|
||||||
zoom_on_scroll: 'स्क्रोलवर झूम करा',
|
zoom_on_scroll: 'स्क्रोलवर झूम करा',
|
||||||
|
show_views: 'डेटाबेस व्ह्यूज',
|
||||||
theme: 'थीम',
|
theme: 'थीम',
|
||||||
show_dependencies: 'डिपेंडेन्सि दाखवा',
|
show_dependencies: 'डिपेंडेन्सि दाखवा',
|
||||||
hide_dependencies: 'डिपेंडेन्सि लपवा',
|
hide_dependencies: 'डिपेंडेन्सि लपवा',
|
||||||
@@ -64,22 +75,13 @@ export const mr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'आरेख पुनःक्रमित करा',
|
title: 'आरेख स्वयंचलित व्यवस्थित करा',
|
||||||
description:
|
description:
|
||||||
'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?',
|
'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?',
|
||||||
reorder: 'पुनःक्रमित करा',
|
reorder: 'स्वयंचलित व्यवस्थित करा',
|
||||||
cancel: 'रद्द करा',
|
cancel: 'रद्द करा',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'एकाधिक स्कीमा',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} स्कीमा या आरेखात आहेत. सध्या दाखवत आहोत: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'पुन्हा दाखवू नका',
|
|
||||||
change_schema: 'बदला',
|
|
||||||
none: 'काहीही नाही',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'कॉपी अयशस्वी',
|
title: 'कॉपी अयशस्वी',
|
||||||
@@ -116,14 +118,11 @@ export const mr: LanguageTranslation = {
|
|||||||
copied: 'Copied!',
|
copied: 'Copied!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'स्कीमा:',
|
|
||||||
filter_by_schema: 'स्कीमा द्वारे फिल्टर करा',
|
|
||||||
search_schema: 'स्कीमा शोधा...',
|
|
||||||
no_schemas_found: 'कोणतेही स्कीमा सापडले नाहीत.',
|
|
||||||
view_all_options: 'सर्व पर्याय पहा...',
|
view_all_options: 'सर्व पर्याय पहा...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'टेबल्स',
|
tables: 'टेबल्स',
|
||||||
add_table: 'टेबल जोडा',
|
add_table: 'टेबल जोडा',
|
||||||
|
add_view: 'व्ह्यू जोडा',
|
||||||
filter: 'फिल्टर',
|
filter: 'फिल्टर',
|
||||||
collapse: 'सर्व संकुचित करा',
|
collapse: 'सर्व संकुचित करा',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -149,16 +148,23 @@ export const mr: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'फील्ड गुणधर्म',
|
title: 'फील्ड गुणधर्म',
|
||||||
unique: 'युनिक',
|
unique: 'युनिक',
|
||||||
|
auto_increment: 'ऑटो इंक्रिमेंट',
|
||||||
comments: 'टिप्पण्या',
|
comments: 'टिप्पण्या',
|
||||||
no_comments: 'कोणत्याही टिप्पणी नाहीत',
|
no_comments: 'कोणत्याही टिप्पणी नाहीत',
|
||||||
delete_field: 'फील्ड हटवा',
|
delete_field: 'फील्ड हटवा',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'अचूकता',
|
||||||
|
scale: 'प्रमाण',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'इंडेक्स गुणधर्म',
|
title: 'इंडेक्स गुणधर्म',
|
||||||
name: 'नाव',
|
name: 'नाव',
|
||||||
unique: 'युनिक',
|
unique: 'युनिक',
|
||||||
|
index_type: 'इंडेक्स प्रकार',
|
||||||
delete_index: 'इंडेक्स हटवा',
|
delete_index: 'इंडेक्स हटवा',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -176,12 +182,15 @@ export const mr: LanguageTranslation = {
|
|||||||
description: 'सुरू करण्यासाठी एक टेबल तयार करा',
|
description: 'सुरू करण्यासाठी एक टेबल तयार करा',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'रिलेशनशिप',
|
refs: 'Refs',
|
||||||
filter: 'फिल्टर',
|
filter: 'फिल्टर',
|
||||||
add_relationship: 'रिलेशनशिप जोडा',
|
|
||||||
collapse: 'सर्व संकुचित करा',
|
collapse: 'सर्व संकुचित करा',
|
||||||
|
add_relationship: 'रिलेशनशिप जोडा',
|
||||||
|
relationships: 'रिलेशनशिप',
|
||||||
|
dependencies: 'डिपेंडेन्सि',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'रिलेशनशिप',
|
||||||
primary: 'प्राथमिक टेबल',
|
primary: 'प्राथमिक टेबल',
|
||||||
foreign: 'रेफरंस टेबल',
|
foreign: 'रेफरंस टेबल',
|
||||||
cardinality: 'कार्डिनॅलिटी',
|
cardinality: 'कार्डिनॅलिटी',
|
||||||
@@ -191,17 +200,8 @@ export const mr: LanguageTranslation = {
|
|||||||
delete_relationship: 'हटवा',
|
delete_relationship: 'हटवा',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'कोणतेही रिलेशनशिप नाहीत',
|
|
||||||
description:
|
|
||||||
'टेबल्स कनेक्ट करण्यासाठी एक रिलेशनशिप तयार करा',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'डिपेंडेन्सि',
|
|
||||||
filter: 'फिल्टर',
|
|
||||||
collapse: 'सर्व संकुचित करा',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'डिपेंडेन्सि',
|
||||||
table: 'टेबल',
|
table: 'टेबल',
|
||||||
dependent_table: 'डिपेंडेन्सि दृश्य',
|
dependent_table: 'डिपेंडेन्सि दृश्य',
|
||||||
delete_dependency: 'हटवा',
|
delete_dependency: 'हटवा',
|
||||||
@@ -211,8 +211,8 @@ export const mr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'कोणत्याही डिपेंडेन्सि नाहीत',
|
title: 'कोणतेही रिलेशनशिप नाहीत',
|
||||||
description: 'सुरू करण्यासाठी एक दृश्य तयार करा',
|
description: 'सुरू करण्यासाठी एक रिलेशनशिप तयार करा',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -236,6 +236,35 @@ export const mr: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuals',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'नोट्स',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'फिल्टर',
|
||||||
|
add_note: 'नोट जोडा',
|
||||||
|
no_results: 'कोणत्याही नोट्स सापडल्या नाहीत',
|
||||||
|
clear: 'फिल्टर साफ करा',
|
||||||
|
empty_state: {
|
||||||
|
title: 'नोट्स नाहीत',
|
||||||
|
description:
|
||||||
|
'कॅनव्हासवर मजकूर भाष्य जोडण्यासाठी एक नोट तयार करा',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'रिकामी नोट',
|
||||||
|
note_actions: {
|
||||||
|
title: 'नोट क्रिया',
|
||||||
|
edit_content: 'सामग्री संपादित करा',
|
||||||
|
delete_note: 'नोट हटवा',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -252,12 +281,16 @@ export const mr: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'कोणतीही enum मूल्ये परिभाषित नाहीत',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -271,8 +304,14 @@ export const mr: LanguageTranslation = {
|
|||||||
show_all: 'सर्व दाखवा',
|
show_all: 'सर्व दाखवा',
|
||||||
undo: 'पूर्ववत करा',
|
undo: 'पूर्ववत करा',
|
||||||
redo: 'पुन्हा करा',
|
redo: 'पुन्हा करा',
|
||||||
reorder_diagram: 'आरेख पुनःक्रमित करा',
|
reorder_diagram: 'आरेख स्वयंचलित व्यवस्थित करा',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'ओव्हरलॅपिंग टेबल्स हायलाइट करा',
|
highlight_overlapping_tables: 'ओव्हरलॅपिंग टेबल्स हायलाइट करा',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -305,13 +344,13 @@ export const mr: LanguageTranslation = {
|
|||||||
// TODO: Add translations
|
// TODO: Add translations
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
back: 'मागे',
|
back: 'मागे',
|
||||||
empty_diagram: 'रिक्त आरेख',
|
empty_diagram: 'रिक्त डेटाबेस',
|
||||||
continue: 'सुरू ठेवा',
|
continue: 'सुरू ठेवा',
|
||||||
import: 'आयात करा',
|
import: 'आयात करा',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'आरेख उघडा',
|
title: 'डेटाबेस उघडा',
|
||||||
description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.',
|
description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'नाव',
|
name: 'नाव',
|
||||||
@@ -321,6 +360,12 @@ export const mr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'रद्द करा',
|
cancel: 'रद्द करा',
|
||||||
open: 'उघडा',
|
open: 'उघडा',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'उघडा',
|
||||||
|
duplicate: 'डुप्लिकेट',
|
||||||
|
delete: 'हटवा',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -407,6 +452,14 @@ export const mr: LanguageTranslation = {
|
|||||||
confirm: 'बदला',
|
confirm: 'बदला',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'नवीन स्कीमा तयार करा',
|
||||||
|
description:
|
||||||
|
'अजून कोणतीही स्कीमा अस्तित्वात नाही. आपल्या टेबल्स व्यवस्थित करण्यासाठी आपली पहिली स्कीमा तयार करा.',
|
||||||
|
create: 'तयार करा',
|
||||||
|
cancel: 'रद्द करा',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'आम्हाला सुधारण्यास मदत करा!',
|
title: 'आम्हाला सुधारण्यास मदत करा!',
|
||||||
description:
|
description:
|
||||||
@@ -465,9 +518,11 @@ export const mr: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'नवीन टेबल',
|
new_table: 'नवीन टेबल',
|
||||||
|
new_view: 'नवीन व्ह्यू',
|
||||||
new_relationship: 'नवीन रिलेशनशिप',
|
new_relationship: 'नवीन रिलेशनशिप',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'नवीन टीप',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -488,6 +543,9 @@ export const mr: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'भाषा बदला',
|
change_language: 'भाषा बदला',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'चालू',
|
||||||
|
off: 'बंद',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const ne: LanguageTranslation = {
|
export const ne: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'नयाँ',
|
||||||
|
browse: 'ब्राउज',
|
||||||
|
tables: 'टेबलहरू',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'निर्भरताहरू',
|
||||||
|
custom_types: 'कस्टम प्रकारहरू',
|
||||||
|
visuals: 'Visuals',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'फाइल',
|
actions: 'कार्यहरू',
|
||||||
new: 'नयाँ',
|
new: 'नयाँ...',
|
||||||
open: 'खोल्नुहोस्',
|
browse: 'ब्राउज गर्नुहोस्...',
|
||||||
save: 'सुरक्षित गर्नुहोस्',
|
save: 'सुरक्षित गर्नुहोस्',
|
||||||
import: 'डाटाबेस आयात गर्नुहोस्',
|
import: 'डाटाबेस आयात गर्नुहोस्',
|
||||||
export_sql: 'SQL निर्यात गर्नुहोस्',
|
export_sql: 'SQL निर्यात गर्नुहोस्',
|
||||||
export_as: 'निर्यात गर्नुहोस्',
|
export_as: 'निर्यात गर्नुहोस्',
|
||||||
delete_diagram: 'डायाग्राम हटाउनुहोस्',
|
delete_diagram: 'हटाउनुहोस्',
|
||||||
exit: 'बाहिर निस्कनुहोस्',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'सम्पादन',
|
edit: 'सम्पादन',
|
||||||
@@ -26,7 +34,10 @@ export const ne: LanguageTranslation = {
|
|||||||
hide_sidebar: 'साइडबार लुकाउनुहोस्',
|
hide_sidebar: 'साइडबार लुकाउनुहोस्',
|
||||||
hide_cardinality: 'कार्डिन्यालिटी लुकाउनुहोस्',
|
hide_cardinality: 'कार्डिन्यालिटी लुकाउनुहोस्',
|
||||||
show_cardinality: 'कार्डिन्यालिटी देखाउनुहोस्',
|
show_cardinality: 'कार्डिन्यालिटी देखाउनुहोस्',
|
||||||
|
hide_field_attributes: 'फिल्ड विशेषताहरू लुकाउनुहोस्',
|
||||||
|
show_field_attributes: 'फिल्ड विशेषताहरू देखाउनुहोस्',
|
||||||
zoom_on_scroll: 'स्क्रोलमा जुम गर्नुहोस्',
|
zoom_on_scroll: 'स्क्रोलमा जुम गर्नुहोस्',
|
||||||
|
show_views: 'डाटाबेस भ्यूहरू',
|
||||||
theme: 'थिम',
|
theme: 'थिम',
|
||||||
show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्',
|
show_dependencies: 'डिपेन्डेन्सीहरू देखाउनुहोस्',
|
||||||
hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्',
|
hide_dependencies: 'डिपेन्डेन्सीहरू लुकाउनुहोस्',
|
||||||
@@ -64,22 +75,13 @@ export const ne: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'डायाग्राम पुनः क्रमबद्ध गर्नुहोस्',
|
title: 'डायाग्राम स्वचालित मिलाउनुहोस्',
|
||||||
description:
|
description:
|
||||||
'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।',
|
'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।',
|
||||||
reorder: 'पुनः क्रमबद्ध गर्नुहोस्',
|
reorder: 'स्वचालित मिलाउनुहोस्',
|
||||||
cancel: 'रद्द गर्नुहोस्',
|
cancel: 'रद्द गर्नुहोस्',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'विविध स्कीमहरू',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} डायाग्राममा स्कीमहरू। हालको रूपमा देखाइएको छ: {{formattedSchemas}}।',
|
|
||||||
dont_show_again: 'फेरि देखाउन नदिनुहोस्',
|
|
||||||
change_schema: 'स्कीम परिवर्तन गर्नुहोस्',
|
|
||||||
none: 'कुनै पनि छैन',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'प्रतिलिपि असफल',
|
title: 'प्रतिलिपि असफल',
|
||||||
@@ -114,14 +116,11 @@ export const ne: LanguageTranslation = {
|
|||||||
copied: 'प्रतिलिपि गरियो!',
|
copied: 'प्रतिलिपि गरियो!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'स्कीम:',
|
|
||||||
filter_by_schema: 'स्कीम अनुसार फिल्टर गर्नुहोस्',
|
|
||||||
search_schema: 'स्कीम खोज्नुहोस्...',
|
|
||||||
no_schemas_found: 'कुनै स्कीमहरू फेला परेनन्',
|
|
||||||
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
|
view_all_options: 'सबै विकल्पहरू हेर्नुहोस्',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'तालिकाहरू',
|
tables: 'तालिकाहरू',
|
||||||
add_table: 'तालिका थप्नुहोस्',
|
add_table: 'तालिका थप्नुहोस्',
|
||||||
|
add_view: 'भ्यू थप्नुहोस्',
|
||||||
filter: 'फिल्टर',
|
filter: 'फिल्टर',
|
||||||
collapse: 'सबै लुकाउनुहोस्',
|
collapse: 'सबै लुकाउनुहोस्',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -147,16 +146,23 @@ export const ne: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'क्षेत्र विशेषताहरू',
|
title: 'क्षेत्र विशेषताहरू',
|
||||||
unique: 'अनन्य',
|
unique: 'अनन्य',
|
||||||
|
auto_increment: 'स्वचालित वृद्धि',
|
||||||
comments: 'टिप्पणीहरू',
|
comments: 'टिप्पणीहरू',
|
||||||
no_comments: 'कुनै टिप्पणीहरू छैनन्',
|
no_comments: 'कुनै टिप्पणीहरू छैनन्',
|
||||||
delete_field: 'क्षेत्र हटाउनुहोस्',
|
delete_field: 'क्षेत्र हटाउनुहोस्',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'परिशुद्धता',
|
||||||
|
scale: 'स्केल',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'सूचक विशेषताहरू',
|
title: 'सूचक विशेषताहरू',
|
||||||
name: 'नाम',
|
name: 'नाम',
|
||||||
unique: 'अनन्य',
|
unique: 'अनन्य',
|
||||||
|
index_type: 'इन्डेक्स प्रकार',
|
||||||
delete_index: 'सूचक हटाउनुहोस्',
|
delete_index: 'सूचक हटाउनुहोस्',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -173,12 +179,15 @@ export const ne: LanguageTranslation = {
|
|||||||
description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्',
|
description: 'सुरु गर्नका लागि एक तालिका बनाउनुहोस्',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'सम्बन्धहरू',
|
refs: 'Refs',
|
||||||
filter: 'फिल्टर',
|
filter: 'फिल्टर',
|
||||||
add_relationship: 'सम्बन्ध थप्नुहोस्',
|
|
||||||
collapse: 'सबै लुकाउनुहोस्',
|
collapse: 'सबै लुकाउनुहोस्',
|
||||||
|
add_relationship: 'सम्बन्ध थप्नुहोस्',
|
||||||
|
relationships: 'सम्बन्धहरू',
|
||||||
|
dependencies: 'डिपेन्डेन्सीहरू',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'सम्बन्ध',
|
||||||
primary: 'मुख्य तालिका',
|
primary: 'मुख्य तालिका',
|
||||||
foreign: 'परिचित तालिका',
|
foreign: 'परिचित तालिका',
|
||||||
cardinality: 'कार्डिन्यालिटी',
|
cardinality: 'कार्डिन्यालिटी',
|
||||||
@@ -188,16 +197,8 @@ export const ne: LanguageTranslation = {
|
|||||||
delete_relationship: 'हटाउनुहोस्',
|
delete_relationship: 'हटाउनुहोस्',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'कुनै सम्बन्धहरू छैनन्',
|
|
||||||
description: 'तालिकाहरू जोड्नका लागि एक सम्बन्ध बनाउनुहोस्',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'डिपेन्डेन्सीहरू',
|
|
||||||
filter: 'फिल्टर',
|
|
||||||
collapse: 'सबै लुकाउनुहोस्',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'डिपेन्डेन्सी',
|
||||||
table: 'तालिका',
|
table: 'तालिका',
|
||||||
dependent_table: 'विचलित तालिका',
|
dependent_table: 'विचलित तालिका',
|
||||||
delete_dependency: 'हटाउनुहोस्',
|
delete_dependency: 'हटाउनुहोस्',
|
||||||
@@ -207,9 +208,8 @@ export const ne: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'कुनै डिपेन्डेन्सीहरू छैनन्',
|
title: 'कुनै सम्बन्धहरू छैनन्',
|
||||||
description:
|
description: 'सुरु गर्नका लागि एक सम्बन्ध बनाउनुहोस्',
|
||||||
'डिपेन्डेन्सीहरू देखाउनका लागि एक व्यू बनाउनुहोस्',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -233,6 +233,35 @@ export const ne: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuals',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'टिप्पणीहरू',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'फिल्टर',
|
||||||
|
add_note: 'टिप्पणी थप्नुहोस्',
|
||||||
|
no_results: 'कुनै टिप्पणी फेला परेन',
|
||||||
|
clear: 'फिल्टर खाली गर्नुहोस्',
|
||||||
|
empty_state: {
|
||||||
|
title: 'कुनै टिप्पणी छैन',
|
||||||
|
description:
|
||||||
|
'क्यानभासमा पाठ टिप्पणी थप्न टिप्पणी सिर्जना गर्नुहोस्',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'खाली टिप्पणी',
|
||||||
|
note_actions: {
|
||||||
|
title: 'टिप्पणी कार्यहरू',
|
||||||
|
edit_content: 'सामग्री सम्पादन गर्नुहोस्',
|
||||||
|
delete_note: 'टिप्पणी मेटाउनुहोस्',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -249,12 +278,16 @@ export const ne: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'कुनै enum मानहरू परिभाषित छैनन्',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -268,9 +301,15 @@ export const ne: LanguageTranslation = {
|
|||||||
show_all: 'सबै देखाउनुहोस्',
|
show_all: 'सबै देखाउनुहोस्',
|
||||||
undo: 'पूर्ववत',
|
undo: 'पूर्ववत',
|
||||||
redo: 'पुनः गर्नुहोस्',
|
redo: 'पुनः गर्नुहोस्',
|
||||||
reorder_diagram: 'पुनः क्रमबद्ध गर्नुहोस्',
|
reorder_diagram: 'डायाग्राम स्वचालित मिलाउनुहोस्',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables:
|
highlight_overlapping_tables:
|
||||||
'अतिरिक्त तालिकाहरू हाइलाइट गर्नुहोस्',
|
'अतिरिक्त तालिकाहरू हाइलाइट गर्नुहोस्',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -301,13 +340,13 @@ export const ne: LanguageTranslation = {
|
|||||||
cancel: 'रद्द गर्नुहोस्',
|
cancel: 'रद्द गर्नुहोस्',
|
||||||
import_from_file: 'फाइलबाट आयात गर्नुहोस्',
|
import_from_file: 'फाइलबाट आयात गर्नुहोस्',
|
||||||
back: 'फर्क',
|
back: 'फर्क',
|
||||||
empty_diagram: 'रिक्त डायाग्राम',
|
empty_diagram: 'खाली डाटाबेस',
|
||||||
continue: 'जारी राख्नुहोस्',
|
continue: 'जारी राख्नुहोस्',
|
||||||
import: 'आयात गर्नुहोस्',
|
import: 'आयात गर्नुहोस्',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'डायाग्राम खोल्नुहोस्',
|
title: 'डाटाबेस खोल्नुहोस्',
|
||||||
description:
|
description:
|
||||||
'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।',
|
'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
@@ -318,6 +357,12 @@ export const ne: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'रद्द गर्नुहोस्',
|
cancel: 'रद्द गर्नुहोस्',
|
||||||
open: 'खोल्नुहोस्',
|
open: 'खोल्नुहोस्',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'खोल्नुहोस्',
|
||||||
|
duplicate: 'डुप्लिकेट',
|
||||||
|
delete: 'मेटाउनुहोस्',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -404,6 +449,14 @@ export const ne: LanguageTranslation = {
|
|||||||
confirm: 'परिवर्तन गर्नुहोस्',
|
confirm: 'परिवर्तन गर्नुहोस्',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'नयाँ स्कीम सिर्जना गर्नुहोस्',
|
||||||
|
description:
|
||||||
|
'अहिलेसम्म कुनै स्कीम अस्तित्वमा छैन। आफ्ना तालिकाहरू व्यवस्थित गर्न आफ्नो पहिलो स्कीम सिर्जना गर्नुहोस्।',
|
||||||
|
create: 'सिर्जना गर्नुहोस्',
|
||||||
|
cancel: 'रद्द गर्नुहोस्',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'हामीलाई अझ राम्रो हुन मदत गर्नुहोस!',
|
title: 'हामीलाई अझ राम्रो हुन मदत गर्नुहोस!',
|
||||||
description:
|
description:
|
||||||
@@ -459,9 +512,11 @@ export const ne: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'नयाँ तालिका',
|
new_table: 'नयाँ तालिका',
|
||||||
|
new_view: 'नयाँ भ्यू',
|
||||||
new_relationship: 'नयाँ सम्बन्ध',
|
new_relationship: 'नयाँ सम्बन्ध',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'नयाँ नोट',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -480,6 +535,9 @@ export const ne: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'भाषा परिवर्तन गर्नुहोस्',
|
change_language: 'भाषा परिवर्तन गर्नुहोस्',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'सक्रिय',
|
||||||
|
off: 'निष्क्रिय',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const pt_BR: LanguageTranslation = {
|
export const pt_BR: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Novo',
|
||||||
|
browse: 'Navegar',
|
||||||
|
tables: 'Tabelas',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Dependências',
|
||||||
|
custom_types: 'Tipos Personalizados',
|
||||||
|
visuals: 'Visuais',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Arquivo',
|
actions: 'Ações',
|
||||||
new: 'Novo',
|
new: 'Novo...',
|
||||||
open: 'Abrir',
|
browse: 'Navegar...',
|
||||||
save: 'Salvar',
|
save: 'Salvar',
|
||||||
import: 'Importar Banco de Dados',
|
import: 'Importar Banco de Dados',
|
||||||
export_sql: 'Exportar SQL',
|
export_sql: 'Exportar SQL',
|
||||||
export_as: 'Exportar como',
|
export_as: 'Exportar como',
|
||||||
delete_diagram: 'Excluir Diagrama',
|
delete_diagram: 'Excluir',
|
||||||
exit: 'Sair',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Editar',
|
edit: 'Editar',
|
||||||
@@ -26,7 +34,10 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Ocultar Barra Lateral',
|
hide_sidebar: 'Ocultar Barra Lateral',
|
||||||
hide_cardinality: 'Ocultar Cardinalidade',
|
hide_cardinality: 'Ocultar Cardinalidade',
|
||||||
show_cardinality: 'Mostrar Cardinalidade',
|
show_cardinality: 'Mostrar Cardinalidade',
|
||||||
|
hide_field_attributes: 'Ocultar Atributos de Campo',
|
||||||
|
show_field_attributes: 'Mostrar Atributos de Campo',
|
||||||
zoom_on_scroll: 'Zoom ao Rolar',
|
zoom_on_scroll: 'Zoom ao Rolar',
|
||||||
|
show_views: 'Visualizações do Banco de Dados',
|
||||||
theme: 'Tema',
|
theme: 'Tema',
|
||||||
show_dependencies: 'Mostrar Dependências',
|
show_dependencies: 'Mostrar Dependências',
|
||||||
hide_dependencies: 'Ocultar Dependências',
|
hide_dependencies: 'Ocultar Dependências',
|
||||||
@@ -64,22 +75,13 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Reordenar Diagrama',
|
title: 'Organizar Diagrama Automaticamente',
|
||||||
description:
|
description:
|
||||||
'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?',
|
'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?',
|
||||||
reorder: 'Reordenar',
|
reorder: 'Organizar Automaticamente',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Múltiplos Esquemas',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} esquemas neste diagrama. Atualmente exibindo: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Não mostrar novamente',
|
|
||||||
change_schema: 'Alterar',
|
|
||||||
none: 'nenhum',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Falha na cópia',
|
title: 'Falha na cópia',
|
||||||
@@ -114,14 +116,11 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
copied: 'Copiado!',
|
copied: 'Copiado!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Esquema:',
|
|
||||||
filter_by_schema: 'Filtrar por esquema',
|
|
||||||
search_schema: 'Buscar esquema...',
|
|
||||||
no_schemas_found: 'Nenhum esquema encontrado.',
|
|
||||||
view_all_options: 'Ver todas as Opções...',
|
view_all_options: 'Ver todas as Opções...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tabelas',
|
tables: 'Tabelas',
|
||||||
add_table: 'Adicionar Tabela',
|
add_table: 'Adicionar Tabela',
|
||||||
|
add_view: 'Adicionar Visualização',
|
||||||
filter: 'Filtrar',
|
filter: 'Filtrar',
|
||||||
collapse: 'Colapsar Todas',
|
collapse: 'Colapsar Todas',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -147,16 +146,23 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Atributos do Campo',
|
title: 'Atributos do Campo',
|
||||||
unique: 'Único',
|
unique: 'Único',
|
||||||
|
auto_increment: 'Incremento Automático',
|
||||||
comments: 'Comentários',
|
comments: 'Comentários',
|
||||||
no_comments: 'Sem comentários',
|
no_comments: 'Sem comentários',
|
||||||
delete_field: 'Excluir Campo',
|
delete_field: 'Excluir Campo',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Precisão',
|
||||||
|
scale: 'Escala',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Atributos do Índice',
|
title: 'Atributos do Índice',
|
||||||
name: 'Nome',
|
name: 'Nome',
|
||||||
unique: 'Único',
|
unique: 'Único',
|
||||||
|
index_type: 'Tipo de Índice',
|
||||||
delete_index: 'Excluir Índice',
|
delete_index: 'Excluir Índice',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -173,12 +179,15 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
description: 'Crie uma tabela para começar',
|
description: 'Crie uma tabela para começar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Relacionamentos',
|
refs: 'Refs',
|
||||||
filter: 'Filtrar',
|
filter: 'Filtrar',
|
||||||
add_relationship: 'Adicionar Relacionamento',
|
|
||||||
collapse: 'Colapsar Todas',
|
collapse: 'Colapsar Todas',
|
||||||
|
add_relationship: 'Adicionar Relacionamento',
|
||||||
|
relationships: 'Relacionamentos',
|
||||||
|
dependencies: 'Dependências',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Relacionamento',
|
||||||
primary: 'Tabela Primária',
|
primary: 'Tabela Primária',
|
||||||
foreign: 'Tabela Referenciada',
|
foreign: 'Tabela Referenciada',
|
||||||
cardinality: 'Cardinalidade',
|
cardinality: 'Cardinalidade',
|
||||||
@@ -188,16 +197,8 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
delete_relationship: 'Excluir',
|
delete_relationship: 'Excluir',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'Sem relacionamentos',
|
|
||||||
description: 'Crie um relacionamento para conectar tabelas',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Dependências',
|
|
||||||
filter: 'Filtrar',
|
|
||||||
collapse: 'Colapsar Todas',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Dependência',
|
||||||
table: 'Tabela',
|
table: 'Tabela',
|
||||||
dependent_table: 'Visualização Dependente',
|
dependent_table: 'Visualização Dependente',
|
||||||
delete_dependency: 'Excluir',
|
delete_dependency: 'Excluir',
|
||||||
@@ -207,8 +208,8 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Sem dependências',
|
title: 'Sem relacionamentos',
|
||||||
description: 'Crie uma visualização para começar',
|
description: 'Crie um relacionamento para começar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -232,6 +233,35 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuais',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Notas',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filtrar',
|
||||||
|
add_note: 'Adicionar Nota',
|
||||||
|
no_results: 'Nenhuma nota encontrada',
|
||||||
|
clear: 'Limpar Filtro',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Sem Notas',
|
||||||
|
description:
|
||||||
|
'Crie uma nota para adicionar anotações de texto na tela',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Nota vazia',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Ações de Nota',
|
||||||
|
edit_content: 'Editar Conteúdo',
|
||||||
|
delete_note: 'Excluir Nota',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -248,12 +278,16 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'Nenhum valor de enum definido',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -267,8 +301,14 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
show_all: 'Mostrar Tudo',
|
show_all: 'Mostrar Tudo',
|
||||||
undo: 'Desfazer',
|
undo: 'Desfazer',
|
||||||
redo: 'Refazer',
|
redo: 'Refazer',
|
||||||
reorder_diagram: 'Reordenar Diagrama',
|
reorder_diagram: 'Organizar Diagrama Automaticamente',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Destacar Tabelas Sobrepostas',
|
highlight_overlapping_tables: 'Destacar Tabelas Sobrepostas',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -300,13 +340,13 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
back: 'Voltar',
|
back: 'Voltar',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
empty_diagram: 'Diagrama vazio',
|
empty_diagram: 'Banco de dados vazio',
|
||||||
continue: 'Continuar',
|
continue: 'Continuar',
|
||||||
import: 'Importar',
|
import: 'Importar',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Abrir Diagrama',
|
title: 'Abrir Banco de Dados',
|
||||||
description: 'Selecione um diagrama para abrir da lista abaixo.',
|
description: 'Selecione um diagrama para abrir da lista abaixo.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'Nome',
|
name: 'Nome',
|
||||||
@@ -316,6 +356,12 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
open: 'Abrir',
|
open: 'Abrir',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Abrir',
|
||||||
|
duplicate: 'Duplicar',
|
||||||
|
delete: 'Excluir',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -402,6 +448,14 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
confirm: 'Alterar',
|
confirm: 'Alterar',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Criar Novo Esquema',
|
||||||
|
description:
|
||||||
|
'Ainda não existem esquemas. Crie seu primeiro esquema para organizar suas tabelas.',
|
||||||
|
create: 'Criar',
|
||||||
|
cancel: 'Cancelar',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Ajude-nos a melhorar!',
|
title: 'Ajude-nos a melhorar!',
|
||||||
description:
|
description:
|
||||||
@@ -457,9 +511,11 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Nova Tabela',
|
new_table: 'Nova Tabela',
|
||||||
|
new_view: 'Nova Visualização',
|
||||||
new_relationship: 'Novo Relacionamento',
|
new_relationship: 'Novo Relacionamento',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Nova Nota',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -479,6 +535,9 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Idioma',
|
change_language: 'Idioma',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Ligado',
|
||||||
|
off: 'Desligado',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const ru: LanguageTranslation = {
|
export const ru: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Новая',
|
||||||
|
browse: 'Обзор',
|
||||||
|
tables: 'Таблицы',
|
||||||
|
refs: 'Ссылки',
|
||||||
|
dependencies: 'Зависимости',
|
||||||
|
custom_types: 'Пользовательские типы',
|
||||||
|
visuals: 'Визуальные элементы',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Файл',
|
actions: 'Действия',
|
||||||
new: 'Создать',
|
new: 'Новая...',
|
||||||
open: 'Открыть',
|
browse: 'Обзор...',
|
||||||
save: 'Сохранить',
|
save: 'Сохранить',
|
||||||
import: 'Импортировать базу данных',
|
import: 'Импортировать базу данных',
|
||||||
export_sql: 'Экспорт SQL',
|
export_sql: 'Экспорт SQL',
|
||||||
export_as: 'Экспортировать как',
|
export_as: 'Экспортировать как',
|
||||||
delete_diagram: 'Удалить диаграмму',
|
delete_diagram: 'Удалить',
|
||||||
exit: 'Выход',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Изменение',
|
edit: 'Изменение',
|
||||||
@@ -26,7 +34,10 @@ export const ru: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Скрыть боковую панель',
|
hide_sidebar: 'Скрыть боковую панель',
|
||||||
hide_cardinality: 'Скрыть виды связи',
|
hide_cardinality: 'Скрыть виды связи',
|
||||||
show_cardinality: 'Показать виды связи',
|
show_cardinality: 'Показать виды связи',
|
||||||
|
show_field_attributes: 'Показать атрибуты поля',
|
||||||
|
hide_field_attributes: 'Скрыть атрибуты поля',
|
||||||
zoom_on_scroll: 'Увеличение при прокрутке',
|
zoom_on_scroll: 'Увеличение при прокрутке',
|
||||||
|
show_views: 'Представления базы данных',
|
||||||
theme: 'Тема',
|
theme: 'Тема',
|
||||||
show_dependencies: 'Показать зависимости',
|
show_dependencies: 'Показать зависимости',
|
||||||
hide_dependencies: 'Скрыть зависимости',
|
hide_dependencies: 'Скрыть зависимости',
|
||||||
@@ -62,22 +73,13 @@ export const ru: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Переупорядочить диаграмму',
|
title: 'Автоматическая расстановка диаграммы',
|
||||||
description:
|
description:
|
||||||
'Это действие переставит все таблицы на диаграмме. Хотите продолжить?',
|
'Это действие переставит все таблицы на диаграмме. Хотите продолжить?',
|
||||||
reorder: 'Изменить порядок',
|
reorder: 'Автоматическая расстановка',
|
||||||
cancel: 'Отменить',
|
cancel: 'Отменить',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Множественные схемы',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} схем в этой диаграмме. В данный момент отображается: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Больше не показывать',
|
|
||||||
change_schema: 'Изменить',
|
|
||||||
none: 'никто',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Ошибка копирования',
|
title: 'Ошибка копирования',
|
||||||
@@ -111,14 +113,11 @@ export const ru: LanguageTranslation = {
|
|||||||
show_less: 'Показать меньше',
|
show_less: 'Показать меньше',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Схема:',
|
|
||||||
filter_by_schema: 'Фильтр по схеме',
|
|
||||||
search_schema: 'Схема поиска...',
|
|
||||||
no_schemas_found: 'Схемы не найдены.',
|
|
||||||
view_all_options: 'Просмотреть все варианты...',
|
view_all_options: 'Просмотреть все варианты...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Таблицы',
|
tables: 'Таблицы',
|
||||||
add_table: 'Добавить таблицу',
|
add_table: 'Добавить таблицу',
|
||||||
|
add_view: 'Добавить представление',
|
||||||
filter: 'Фильтр',
|
filter: 'Фильтр',
|
||||||
collapse: 'Свернуть все',
|
collapse: 'Свернуть все',
|
||||||
clear: 'Очистить фильтр',
|
clear: 'Очистить фильтр',
|
||||||
@@ -144,15 +143,22 @@ export const ru: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Атрибуты поля',
|
title: 'Атрибуты поля',
|
||||||
unique: 'Уникальный',
|
unique: 'Уникальный',
|
||||||
|
auto_increment: 'Автоинкремент',
|
||||||
comments: 'Комментарии',
|
comments: 'Комментарии',
|
||||||
no_comments: 'Нет комментария',
|
no_comments: 'Нет комментария',
|
||||||
delete_field: 'Удалить поле',
|
delete_field: 'Удалить поле',
|
||||||
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
character_length: 'Макс. длина',
|
character_length: 'Макс. длина',
|
||||||
|
precision: 'Точность',
|
||||||
|
scale: 'Масштаб',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Атрибуты индекса',
|
title: 'Атрибуты индекса',
|
||||||
name: 'Имя',
|
name: 'Имя',
|
||||||
unique: 'Уникальный',
|
unique: 'Уникальный',
|
||||||
|
index_type: 'Тип индекса',
|
||||||
delete_index: 'Удалить индекс',
|
delete_index: 'Удалить индекс',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -169,12 +175,15 @@ export const ru: LanguageTranslation = {
|
|||||||
description: 'Создайте таблицу, чтобы начать',
|
description: 'Создайте таблицу, чтобы начать',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Отношения',
|
refs: 'Ссылки',
|
||||||
filter: 'Фильтр',
|
filter: 'Фильтр',
|
||||||
add_relationship: 'Добавить отношение',
|
|
||||||
collapse: 'Свернуть все',
|
collapse: 'Свернуть все',
|
||||||
|
add_relationship: 'Добавить отношение',
|
||||||
|
relationships: 'Отношения',
|
||||||
|
dependencies: 'Зависимости',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Отношение',
|
||||||
primary: 'Основная таблица',
|
primary: 'Основная таблица',
|
||||||
foreign: 'Справочная таблица',
|
foreign: 'Справочная таблица',
|
||||||
cardinality: 'Тип множественной связи',
|
cardinality: 'Тип множественной связи',
|
||||||
@@ -184,18 +193,10 @@ export const ru: LanguageTranslation = {
|
|||||||
delete_relationship: 'Удалить',
|
delete_relationship: 'Удалить',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'Нет отношений',
|
|
||||||
description: 'Создайте связь для соединения таблиц',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Зависимости',
|
|
||||||
filter: 'Фильтр',
|
|
||||||
collapse: 'Свернуть все',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
table: 'Стол',
|
dependency: 'Зависимость',
|
||||||
dependent_table: 'Зависимый вид',
|
table: 'Таблица',
|
||||||
|
dependent_table: 'Зависимое представление',
|
||||||
delete_dependency: 'Удалить',
|
delete_dependency: 'Удалить',
|
||||||
dependency_actions: {
|
dependency_actions: {
|
||||||
title: 'Действия',
|
title: 'Действия',
|
||||||
@@ -203,8 +204,8 @@ export const ru: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Нет зависимостей',
|
title: 'Нет отношений',
|
||||||
description: 'Создайте представление, чтобы начать',
|
description: 'Создайте отношение, чтобы начать',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -229,6 +230,35 @@ export const ru: LanguageTranslation = {
|
|||||||
description: 'Создайте область, чтобы начать',
|
description: 'Создайте область, чтобы начать',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Визуальные элементы',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Области',
|
||||||
|
notes: 'Заметки',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Фильтр',
|
||||||
|
add_note: 'Добавить Заметку',
|
||||||
|
no_results: 'Заметки не найдены',
|
||||||
|
clear: 'Очистить Фильтр',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Нет Заметок',
|
||||||
|
description:
|
||||||
|
'Создайте заметку, чтобы добавить текстовые аннотации на холсте',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Пустая заметка',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Действия с Заметкой',
|
||||||
|
edit_content: 'Редактировать Содержимое',
|
||||||
|
delete_note: 'Удалить Заметку',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -245,12 +275,16 @@ export const ru: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'Значения перечисления не определены',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -264,8 +298,14 @@ export const ru: LanguageTranslation = {
|
|||||||
show_all: 'Показать все',
|
show_all: 'Показать все',
|
||||||
undo: 'Отменить',
|
undo: 'Отменить',
|
||||||
redo: 'Вернуть',
|
redo: 'Вернуть',
|
||||||
reorder_diagram: 'Переупорядочить диаграмму',
|
reorder_diagram: 'Автоматическая расстановка диаграммы',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Выделение перекрывающихся таблиц',
|
highlight_overlapping_tables: 'Выделение перекрывающихся таблиц',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -296,13 +336,13 @@ export const ru: LanguageTranslation = {
|
|||||||
cancel: 'Отменить',
|
cancel: 'Отменить',
|
||||||
back: 'Назад',
|
back: 'Назад',
|
||||||
import_from_file: 'Импортировать из файла',
|
import_from_file: 'Импортировать из файла',
|
||||||
empty_diagram: 'Пустая диаграмма',
|
empty_diagram: 'Пустая база данных',
|
||||||
continue: 'Продолжить',
|
continue: 'Продолжить',
|
||||||
import: 'Импорт',
|
import: 'Импорт',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Открыть диаграмму',
|
title: 'Открыть базу данных',
|
||||||
description:
|
description:
|
||||||
'Выберите диаграмму, которую нужно открыть, из списка ниже.',
|
'Выберите диаграмму, которую нужно открыть, из списка ниже.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
@@ -313,6 +353,12 @@ export const ru: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Отмена',
|
cancel: 'Отмена',
|
||||||
open: 'Открыть',
|
open: 'Открыть',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Открыть',
|
||||||
|
duplicate: 'Дублировать',
|
||||||
|
delete: 'Удалить',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -399,6 +445,14 @@ export const ru: LanguageTranslation = {
|
|||||||
confirm: 'Изменить',
|
confirm: 'Изменить',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Создать новую схему',
|
||||||
|
description:
|
||||||
|
'Схемы еще не существуют. Создайте вашу первую схему, чтобы организовать таблицы.',
|
||||||
|
create: 'Создать',
|
||||||
|
cancel: 'Отменить',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Помогите нам стать лучше!',
|
title: 'Помогите нам стать лучше!',
|
||||||
description:
|
description:
|
||||||
@@ -453,8 +507,10 @@ export const ru: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Создать таблицу',
|
new_table: 'Создать таблицу',
|
||||||
|
new_view: 'Новое представление',
|
||||||
new_relationship: 'Создать отношение',
|
new_relationship: 'Создать отношение',
|
||||||
new_area: 'Новая область',
|
new_area: 'Новая область',
|
||||||
|
new_note: 'Новая Заметка',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -474,6 +530,9 @@ export const ru: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Сменить язык',
|
change_language: 'Сменить язык',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Вкл',
|
||||||
|
off: 'Выкл',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const te: LanguageTranslation = {
|
export const te: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'కొత్తది',
|
||||||
|
browse: 'బ్రాఉజ్',
|
||||||
|
tables: 'టేబల్లు',
|
||||||
|
refs: 'సంబంధాలు',
|
||||||
|
dependencies: 'ఆధారతలు',
|
||||||
|
custom_types: 'కస్టమ్ టైప్స్',
|
||||||
|
visuals: 'Visuals',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'ఫైల్',
|
actions: 'చర్యలు',
|
||||||
new: 'కొత్తది',
|
new: 'కొత్తది...',
|
||||||
open: 'తెరవు',
|
browse: 'బ్రాఉజ్ చేయండి...',
|
||||||
save: 'సేవ్',
|
save: 'సేవ్',
|
||||||
import: 'డేటాబేస్ను దిగుమతి చేసుకోండి',
|
import: 'డేటాబేస్ను దిగుమతి చేసుకోండి',
|
||||||
export_sql: 'SQL ఎగుమతి',
|
export_sql: 'SQL ఎగుమతి',
|
||||||
export_as: 'వగా ఎగుమతి చేయండి',
|
export_as: 'వగా ఎగుమతి చేయండి',
|
||||||
delete_diagram: 'చిత్రాన్ని తొలగించండి',
|
delete_diagram: 'తొలగించండి',
|
||||||
exit: 'నిష్క్రమించు',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'సవరించు',
|
edit: 'సవరించు',
|
||||||
@@ -26,7 +34,10 @@ export const te: LanguageTranslation = {
|
|||||||
hide_sidebar: 'సైడ్బార్ దాచండి',
|
hide_sidebar: 'సైడ్బార్ దాచండి',
|
||||||
hide_cardinality: 'కార్డినాలిటీని దాచండి',
|
hide_cardinality: 'కార్డినాలిటీని దాచండి',
|
||||||
show_cardinality: 'కార్డినాలిటీని చూపించండి',
|
show_cardinality: 'కార్డినాలిటీని చూపించండి',
|
||||||
|
show_field_attributes: 'ఫీల్డ్ గుణాలను చూపించు',
|
||||||
|
hide_field_attributes: 'ఫీల్డ్ గుణాలను దాచండి',
|
||||||
zoom_on_scroll: 'స్క్రోల్పై జూమ్',
|
zoom_on_scroll: 'స్క్రోల్పై జూమ్',
|
||||||
|
show_views: 'డేటాబేస్ వ్యూలు',
|
||||||
theme: 'థీమ్',
|
theme: 'థీమ్',
|
||||||
show_dependencies: 'ఆధారాలు చూపించండి',
|
show_dependencies: 'ఆధారాలు చూపించండి',
|
||||||
hide_dependencies: 'ఆధారాలను దాచండి',
|
hide_dependencies: 'ఆధారాలను దాచండి',
|
||||||
@@ -64,22 +75,13 @@ export const te: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'చిత్రాన్ని పునఃసరిచేయండి',
|
title: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
|
||||||
description:
|
description:
|
||||||
'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?',
|
'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?',
|
||||||
reorder: 'పునఃసరిచేయండి',
|
reorder: 'స్వయంచాలకంగా అమర్చండి',
|
||||||
cancel: 'రద్దు',
|
cancel: 'రద్దు',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'బహుళ స్కీమాలు',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} స్కీమాలు ఈ చిత్రంలో ఉన్నాయి. ప్రస్తుత స్కీమాలు: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'మరలా చూపించవద్దు',
|
|
||||||
change_schema: 'మార్చు',
|
|
||||||
none: 'ఎదరికాదు',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'కాపీ విఫలమైంది',
|
title: 'కాపీ విఫలమైంది',
|
||||||
@@ -114,14 +116,11 @@ export const te: LanguageTranslation = {
|
|||||||
copied: 'కాపీ చేయబడింది!',
|
copied: 'కాపీ చేయబడింది!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'స్కీమా:',
|
|
||||||
filter_by_schema: 'స్కీమా ద్వారా ఫిల్టర్ చేయండి',
|
|
||||||
search_schema: 'స్కీమా కోసం శోధించండి...',
|
|
||||||
no_schemas_found: 'ఏ స్కీమాలు కూడా కనుగొనబడలేదు.',
|
|
||||||
view_all_options: 'అన్ని ఎంపికలను చూడండి...',
|
view_all_options: 'అన్ని ఎంపికలను చూడండి...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'పట్టికలు',
|
tables: 'పట్టికలు',
|
||||||
add_table: 'పట్టికను జోడించు',
|
add_table: 'పట్టికను జోడించు',
|
||||||
|
add_view: 'వ్యూ జోడించండి',
|
||||||
filter: 'ఫిల్టర్',
|
filter: 'ఫిల్టర్',
|
||||||
collapse: 'అన్ని కూల్ చేయి',
|
collapse: 'అన్ని కూల్ చేయి',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -147,16 +146,23 @@ export const te: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'ఫీల్డ్ గుణాలు',
|
title: 'ఫీల్డ్ గుణాలు',
|
||||||
unique: 'అద్వితీయ',
|
unique: 'అద్వితీయ',
|
||||||
|
auto_increment: 'ఆటో ఇంక్రిమెంట్',
|
||||||
comments: 'వ్యాఖ్యలు',
|
comments: 'వ్యాఖ్యలు',
|
||||||
no_comments: 'వ్యాఖ్యలు లేవు',
|
no_comments: 'వ్యాఖ్యలు లేవు',
|
||||||
delete_field: 'ఫీల్డ్ తొలగించు',
|
delete_field: 'ఫీల్డ్ తొలగించు',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'సూక్ష్మత',
|
||||||
|
scale: 'స్కేల్',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'ఇండెక్స్ గుణాలు',
|
title: 'ఇండెక్స్ గుణాలు',
|
||||||
name: 'పేరు',
|
name: 'పేరు',
|
||||||
unique: 'అద్వితీయ',
|
unique: 'అద్వితీయ',
|
||||||
|
index_type: 'ఇండెక్స్ రకం',
|
||||||
delete_index: 'ఇండెక్స్ తొలగించు',
|
delete_index: 'ఇండెక్స్ తొలగించు',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -174,12 +180,15 @@ export const te: LanguageTranslation = {
|
|||||||
description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి',
|
description: 'ప్రారంభించడానికి ఒక పట్టిక సృష్టించండి',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'సంబంధాలు',
|
refs: 'Refs',
|
||||||
filter: 'ఫిల్టర్',
|
filter: 'ఫిల్టర్',
|
||||||
add_relationship: 'సంబంధం జోడించు',
|
|
||||||
collapse: 'అన్ని కూల్ చేయి',
|
collapse: 'అన్ని కూల్ చేయి',
|
||||||
|
add_relationship: 'సంబంధం జోడించు',
|
||||||
|
relationships: 'సంబంధాలు',
|
||||||
|
dependencies: 'ఆధారాలు',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'సంబంధం',
|
||||||
primary: 'ప్రాథమిక పట్టిక',
|
primary: 'ప్రాథమిక పట్టిక',
|
||||||
foreign: 'సూచించబడిన పట్టిక',
|
foreign: 'సూచించబడిన పట్టిక',
|
||||||
cardinality: 'కార్డినాలిటీ',
|
cardinality: 'కార్డినాలిటీ',
|
||||||
@@ -189,16 +198,8 @@ export const te: LanguageTranslation = {
|
|||||||
delete_relationship: 'సంబంధం తొలగించు',
|
delete_relationship: 'సంబంధం తొలగించు',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'సంబంధాలు లేవు',
|
|
||||||
description: 'పట్టికలను అనుసంధించడానికి సంబంధం సృష్టించండి',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'ఆధారాలు',
|
|
||||||
filter: 'ఫిల్టర్',
|
|
||||||
collapse: 'అన్ని కూల్ చేయి',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'ఆధారం',
|
||||||
table: 'పట్టిక',
|
table: 'పట్టిక',
|
||||||
dependent_table: 'ఆధారిత వీక్షణ',
|
dependent_table: 'ఆధారిత వీక్షణ',
|
||||||
delete_dependency: 'ఆధారాన్ని తొలగించు',
|
delete_dependency: 'ఆధారాన్ని తొలగించు',
|
||||||
@@ -208,8 +209,8 @@ export const te: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'ఆధారాలు లేవు',
|
title: 'సంబంధాలు లేవు',
|
||||||
description: 'ప్రారంభించడానికి ఒక వీక్షణ సృష్టించండి',
|
description: 'ప్రారంభించడానికి ఒక సంబంధం సృష్టించండి',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -233,6 +234,35 @@ export const te: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Visuals',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'గమనికలు',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'ఫిల్టర్',
|
||||||
|
add_note: 'గమనిక జోడించండి',
|
||||||
|
no_results: 'గమనికలు కనుగొనబడలేదు',
|
||||||
|
clear: 'ఫిల్టర్ను క్లియర్ చేయండి',
|
||||||
|
empty_state: {
|
||||||
|
title: 'గమనికలు లేవు',
|
||||||
|
description:
|
||||||
|
'కాన్వాస్పై టెక్స్ట్ ఉల్లేఖనలను జోడించడానికి ఒక గమనికను సృష్టించండి',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'ఖాళీ గమనిక',
|
||||||
|
note_actions: {
|
||||||
|
title: 'గమనిక చర్యలు',
|
||||||
|
edit_content: 'కంటెంట్ను సవరించండి',
|
||||||
|
delete_note: 'గమనికను తొలగించండి',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -249,12 +279,16 @@ export const te: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'ఏ enum విలువలు నిర్వచించబడలేదు',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -268,8 +302,14 @@ export const te: LanguageTranslation = {
|
|||||||
show_all: 'అన్ని చూపించు',
|
show_all: 'అన్ని చూపించు',
|
||||||
undo: 'తిరిగి చేయు',
|
undo: 'తిరిగి చేయు',
|
||||||
redo: 'మరలా చేయు',
|
redo: 'మరలా చేయు',
|
||||||
reorder_diagram: 'చిత్రాన్ని పునఃసరిచేయండి',
|
reorder_diagram: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'అవకాశించు పట్టికలను హైలైట్ చేయండి',
|
highlight_overlapping_tables: 'అవకాశించు పట్టికలను హైలైట్ చేయండి',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -301,13 +341,13 @@ export const te: LanguageTranslation = {
|
|||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
back: 'తిరుగు',
|
back: 'తిరుగు',
|
||||||
empty_diagram: 'ఖాళీ చిత్రము',
|
empty_diagram: 'ఖాళీ డేటాబేస్',
|
||||||
continue: 'కొనసాగించు',
|
continue: 'కొనసాగించు',
|
||||||
import: 'డిగుమతి',
|
import: 'డిగుమతి',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'చిత్రం తెరవండి',
|
title: 'డేటాబేస్ తెరవండి',
|
||||||
description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.',
|
description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'పేరు',
|
name: 'పేరు',
|
||||||
@@ -317,6 +357,12 @@ export const te: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'రద్దు',
|
cancel: 'రద్దు',
|
||||||
open: 'తెరవు',
|
open: 'తెరవు',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'తెరవు',
|
||||||
|
duplicate: 'నకలు',
|
||||||
|
delete: 'తొలగించు',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -403,6 +449,14 @@ export const te: LanguageTranslation = {
|
|||||||
confirm: 'మార్చు',
|
confirm: 'మార్చు',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'కొత్త స్కీమా సృష్టించండి',
|
||||||
|
description:
|
||||||
|
'ఇంకా ఏ స్కీమాలు లేవు. మీ పట్టికలను వ్యవస్థీకరించడానికి మీ మొదటి స్కీమాను సృష్టించండి.',
|
||||||
|
create: 'సృష్టించు',
|
||||||
|
cancel: 'రద్దు',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'మా సహాయంతో మెరుగుపరచండి!',
|
title: 'మా సహాయంతో మెరుగుపరచండి!',
|
||||||
description:
|
description:
|
||||||
@@ -461,9 +515,11 @@ export const te: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'కొత్త పట్టిక',
|
new_table: 'కొత్త పట్టిక',
|
||||||
|
new_view: 'కొత్త వ్యూ',
|
||||||
new_relationship: 'కొత్త సంబంధం',
|
new_relationship: 'కొత్త సంబంధం',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'కొత్త నోట్',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -484,6 +540,9 @@ export const te: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'భాష మార్చు',
|
change_language: 'భాష మార్చు',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'ఆన్',
|
||||||
|
off: 'ఆఫ్',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const tr: LanguageTranslation = {
|
export const tr: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Yeni',
|
||||||
|
browse: 'Gözat',
|
||||||
|
tables: 'Tablolar',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Bağımlılıklar',
|
||||||
|
custom_types: 'Özel Tipler',
|
||||||
|
visuals: 'Görseller',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Dosya',
|
actions: 'Eylemler',
|
||||||
new: 'Yeni',
|
new: 'Yeni...',
|
||||||
open: 'Aç',
|
browse: 'Gözat...',
|
||||||
save: 'Kaydet',
|
save: 'Kaydet',
|
||||||
import: 'Veritabanı İçe Aktar',
|
import: 'Veritabanı İçe Aktar',
|
||||||
export_sql: 'SQL Olarak Dışa Aktar',
|
export_sql: 'SQL Olarak Dışa Aktar',
|
||||||
export_as: 'Olarak Dışa Aktar',
|
export_as: 'Olarak Dışa Aktar',
|
||||||
delete_diagram: 'Diyagramı Sil',
|
delete_diagram: 'Sil',
|
||||||
exit: 'Çıkış',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Düzenle',
|
edit: 'Düzenle',
|
||||||
@@ -26,7 +34,10 @@ export const tr: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Kenar Çubuğunu Gizle',
|
hide_sidebar: 'Kenar Çubuğunu Gizle',
|
||||||
hide_cardinality: 'Kardinaliteyi Gizle',
|
hide_cardinality: 'Kardinaliteyi Gizle',
|
||||||
show_cardinality: 'Kardinaliteyi Göster',
|
show_cardinality: 'Kardinaliteyi Göster',
|
||||||
|
show_field_attributes: 'Alan Özelliklerini Göster',
|
||||||
|
hide_field_attributes: 'Alan Özelliklerini Gizle',
|
||||||
zoom_on_scroll: 'Kaydırarak Yakınlaştır',
|
zoom_on_scroll: 'Kaydırarak Yakınlaştır',
|
||||||
|
show_views: 'Veritabanı Görünümleri',
|
||||||
theme: 'Tema',
|
theme: 'Tema',
|
||||||
show_dependencies: 'Bağımlılıkları Göster',
|
show_dependencies: 'Bağımlılıkları Göster',
|
||||||
hide_dependencies: 'Bağımlılıkları Gizle',
|
hide_dependencies: 'Bağımlılıkları Gizle',
|
||||||
@@ -64,22 +75,13 @@ export const tr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Diyagramı Yeniden Sırala',
|
title: 'Diyagramı Otomatik Düzenle',
|
||||||
description:
|
description:
|
||||||
'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?',
|
'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?',
|
||||||
reorder: 'Yeniden Sırala',
|
reorder: 'Otomatik Düzenle',
|
||||||
cancel: 'İptal',
|
cancel: 'İptal',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Birden Fazla Şema',
|
|
||||||
description:
|
|
||||||
'Bu diyagramda {{schemasCount}} şema var. Şu anda görüntülenen: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Tekrar gösterme',
|
|
||||||
change_schema: 'Değiştir',
|
|
||||||
none: 'yok',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Kopyalama başarısız',
|
title: 'Kopyalama başarısız',
|
||||||
@@ -113,14 +115,11 @@ export const tr: LanguageTranslation = {
|
|||||||
copy_to_clipboard: 'Panoya Kopyala',
|
copy_to_clipboard: 'Panoya Kopyala',
|
||||||
copied: 'Kopyalandı!',
|
copied: 'Kopyalandı!',
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Şema:',
|
|
||||||
filter_by_schema: 'Şemaya Göre Filtrele',
|
|
||||||
search_schema: 'Şema ara...',
|
|
||||||
no_schemas_found: 'Şema bulunamadı.',
|
|
||||||
view_all_options: 'Tüm Seçenekleri Gör...',
|
view_all_options: 'Tüm Seçenekleri Gör...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Tablolar',
|
tables: 'Tablolar',
|
||||||
add_table: 'Tablo Ekle',
|
add_table: 'Tablo Ekle',
|
||||||
|
add_view: 'Görünüm Ekle',
|
||||||
filter: 'Filtrele',
|
filter: 'Filtrele',
|
||||||
collapse: 'Hepsini Daralt',
|
collapse: 'Hepsini Daralt',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -146,16 +145,23 @@ export const tr: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Alan Özellikleri',
|
title: 'Alan Özellikleri',
|
||||||
unique: 'Tekil',
|
unique: 'Tekil',
|
||||||
|
auto_increment: 'Otomatik Artış',
|
||||||
comments: 'Yorumlar',
|
comments: 'Yorumlar',
|
||||||
no_comments: 'Yorum yok',
|
no_comments: 'Yorum yok',
|
||||||
delete_field: 'Alanı Sil',
|
delete_field: 'Alanı Sil',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Hassasiyet',
|
||||||
|
scale: 'Ölçek',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'İndeks Özellikleri',
|
title: 'İndeks Özellikleri',
|
||||||
name: 'Ad',
|
name: 'Ad',
|
||||||
unique: 'Tekil',
|
unique: 'Tekil',
|
||||||
|
index_type: 'İndeks Türü',
|
||||||
delete_index: 'İndeksi Sil',
|
delete_index: 'İndeksi Sil',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -173,12 +179,15 @@ export const tr: LanguageTranslation = {
|
|||||||
description: 'Başlamak için bir tablo oluşturun',
|
description: 'Başlamak için bir tablo oluşturun',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'İlişkiler',
|
refs: 'Refs',
|
||||||
filter: 'Filtrele',
|
filter: 'Filtrele',
|
||||||
add_relationship: 'İlişki Ekle',
|
|
||||||
collapse: 'Hepsini Daralt',
|
collapse: 'Hepsini Daralt',
|
||||||
|
add_relationship: 'İlişki Ekle',
|
||||||
|
relationships: 'İlişkiler',
|
||||||
|
dependencies: 'Bağımlılıklar',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'İlişki',
|
||||||
primary: 'Birincil Tablo',
|
primary: 'Birincil Tablo',
|
||||||
foreign: 'Referans Tablo',
|
foreign: 'Referans Tablo',
|
||||||
cardinality: 'Kardinalite',
|
cardinality: 'Kardinalite',
|
||||||
@@ -188,16 +197,8 @@ export const tr: LanguageTranslation = {
|
|||||||
delete_relationship: 'Sil',
|
delete_relationship: 'Sil',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'İlişki yok',
|
|
||||||
description: 'Tabloları bağlamak için bir ilişki oluşturun',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Bağımlılıklar',
|
|
||||||
filter: 'Filtrele',
|
|
||||||
collapse: 'Hepsini Daralt',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Bağımlılık',
|
||||||
table: 'Tablo',
|
table: 'Tablo',
|
||||||
dependent_table: 'Bağımlı Görünüm',
|
dependent_table: 'Bağımlı Görünüm',
|
||||||
delete_dependency: 'Sil',
|
delete_dependency: 'Sil',
|
||||||
@@ -207,8 +208,8 @@ export const tr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Bağımlılık yok',
|
title: 'İlişki yok',
|
||||||
description: 'Başlamak için bir görünüm oluşturun',
|
description: 'Başlamak için bir ilişki oluşturun',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -232,6 +233,35 @@ export const tr: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Görseller',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Notlar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Filtrele',
|
||||||
|
add_note: 'Not Ekle',
|
||||||
|
no_results: 'Not bulunamadı',
|
||||||
|
clear: 'Filtreyi Temizle',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Not Yok',
|
||||||
|
description:
|
||||||
|
'Tuval üzerinde metin açıklamaları eklemek için bir not oluşturun',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Boş not',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Not İşlemleri',
|
||||||
|
edit_content: 'İçeriği Düzenle',
|
||||||
|
delete_note: 'Notu Sil',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -248,12 +278,16 @@ export const tr: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'Tanımlanmış enum değeri yok',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -266,8 +300,14 @@ export const tr: LanguageTranslation = {
|
|||||||
show_all: 'Hepsini Gör',
|
show_all: 'Hepsini Gör',
|
||||||
undo: 'Geri Al',
|
undo: 'Geri Al',
|
||||||
redo: 'Yinele',
|
redo: 'Yinele',
|
||||||
reorder_diagram: 'Diyagramı Yeniden Sırala',
|
reorder_diagram: 'Diyagramı Otomatik Düzenle',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Çakışan Tabloları Vurgula',
|
highlight_overlapping_tables: 'Çakışan Tabloları Vurgula',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
database_selection: {
|
database_selection: {
|
||||||
@@ -297,12 +337,12 @@ export const tr: LanguageTranslation = {
|
|||||||
import_from_file: 'Import from File',
|
import_from_file: 'Import from File',
|
||||||
cancel: 'İptal',
|
cancel: 'İptal',
|
||||||
back: 'Geri',
|
back: 'Geri',
|
||||||
empty_diagram: 'Boş diyagram',
|
empty_diagram: 'Boş veritabanı',
|
||||||
continue: 'Devam',
|
continue: 'Devam',
|
||||||
import: 'İçe Aktar',
|
import: 'İçe Aktar',
|
||||||
},
|
},
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Diyagramı Aç',
|
title: 'Veritabanı Aç',
|
||||||
description: 'Aşağıdaki listeden açmak için bir diyagram seçin.',
|
description: 'Aşağıdaki listeden açmak için bir diyagram seçin.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'Ad',
|
name: 'Ad',
|
||||||
@@ -312,6 +352,12 @@ export const tr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'İptal',
|
cancel: 'İptal',
|
||||||
open: 'Aç',
|
open: 'Aç',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Aç',
|
||||||
|
duplicate: 'Kopyala',
|
||||||
|
delete: 'Sil',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -392,6 +438,14 @@ export const tr: LanguageTranslation = {
|
|||||||
cancel: 'İptal',
|
cancel: 'İptal',
|
||||||
confirm: 'Değiştir',
|
confirm: 'Değiştir',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Yeni Şema Oluştur',
|
||||||
|
description:
|
||||||
|
'Henüz hiç şema mevcut değil. Tablolarınızı düzenlemek için ilk şemanızı oluşturun.',
|
||||||
|
create: 'Oluştur',
|
||||||
|
cancel: 'İptal',
|
||||||
|
},
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Bize yardım et!',
|
title: 'Bize yardım et!',
|
||||||
description:
|
description:
|
||||||
@@ -446,9 +500,11 @@ export const tr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Yeni Tablo',
|
new_table: 'Yeni Tablo',
|
||||||
|
new_view: 'Yeni Görünüm',
|
||||||
new_relationship: 'Yeni İlişki',
|
new_relationship: 'Yeni İlişki',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Yeni Not',
|
||||||
},
|
},
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Tabloyu Düzenle',
|
edit_table: 'Tabloyu Düzenle',
|
||||||
@@ -468,6 +524,9 @@ export const tr: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Dil',
|
change_language: 'Dil',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Açık',
|
||||||
|
off: 'Kapalı',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const uk: LanguageTranslation = {
|
export const uk: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Нова',
|
||||||
|
browse: 'Огляд',
|
||||||
|
tables: 'Таблиці',
|
||||||
|
refs: 'Зв’язки',
|
||||||
|
dependencies: 'Залежності',
|
||||||
|
custom_types: 'Користувацькі типи',
|
||||||
|
visuals: 'Візуальні елементи',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Файл',
|
actions: 'Дії',
|
||||||
new: 'Новий',
|
new: 'Нова...',
|
||||||
open: 'Відкрити',
|
browse: 'Огляд...',
|
||||||
save: 'Зберегти',
|
save: 'Зберегти',
|
||||||
import: 'Імпорт бази даних',
|
import: 'Імпорт бази даних',
|
||||||
export_sql: 'Експорт SQL',
|
export_sql: 'Експорт SQL',
|
||||||
export_as: 'Експортувати як',
|
export_as: 'Експортувати як',
|
||||||
delete_diagram: 'Видалити діаграму',
|
delete_diagram: 'Видалити',
|
||||||
exit: 'Вийти',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Редагувати',
|
edit: 'Редагувати',
|
||||||
@@ -26,7 +34,10 @@ export const uk: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Приховати бічну панель',
|
hide_sidebar: 'Приховати бічну панель',
|
||||||
hide_cardinality: 'Приховати потужність',
|
hide_cardinality: 'Приховати потужність',
|
||||||
show_cardinality: 'Показати кардинальність',
|
show_cardinality: 'Показати кардинальність',
|
||||||
|
show_field_attributes: 'Показати атрибути полів',
|
||||||
|
hide_field_attributes: 'Приховати атрибути полів',
|
||||||
zoom_on_scroll: 'Масштабувати прокручуванням',
|
zoom_on_scroll: 'Масштабувати прокручуванням',
|
||||||
|
show_views: 'Представлення бази даних',
|
||||||
theme: 'Тема',
|
theme: 'Тема',
|
||||||
show_dependencies: 'Показати залежності',
|
show_dependencies: 'Показати залежності',
|
||||||
hide_dependencies: 'Приховати залежності',
|
hide_dependencies: 'Приховати залежності',
|
||||||
@@ -62,22 +73,13 @@ export const uk: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Перевпорядкувати діаграму',
|
title: 'Автоматичне розміщення діаграми',
|
||||||
description:
|
description:
|
||||||
'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?',
|
'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?',
|
||||||
reorder: 'Перевпорядкувати',
|
reorder: 'Автоматичне розміщення',
|
||||||
cancel: 'Скасувати',
|
cancel: 'Скасувати',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Кілька схем',
|
|
||||||
description:
|
|
||||||
'{{schemasCount}} схеми на цій діаграмі. Зараз відображається: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Більше не показувати',
|
|
||||||
change_schema: 'Зміна',
|
|
||||||
none: 'немає',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Помилка копіювання',
|
title: 'Помилка копіювання',
|
||||||
@@ -112,14 +114,11 @@ export const uk: LanguageTranslation = {
|
|||||||
copied: 'Скопійовано!',
|
copied: 'Скопійовано!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Схема:',
|
|
||||||
filter_by_schema: 'Фільтрувати за схемою',
|
|
||||||
search_schema: 'Пошук схеми…',
|
|
||||||
no_schemas_found: 'Схеми не знайдено.',
|
|
||||||
view_all_options: 'Переглянути всі параметри…',
|
view_all_options: 'Переглянути всі параметри…',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Таблиці',
|
tables: 'Таблиці',
|
||||||
add_table: 'Додати таблицю',
|
add_table: 'Додати таблицю',
|
||||||
|
add_view: 'Додати представлення',
|
||||||
filter: 'Фільтр',
|
filter: 'Фільтр',
|
||||||
collapse: 'Згорнути все',
|
collapse: 'Згорнути все',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -145,16 +144,23 @@ export const uk: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Атрибути полів',
|
title: 'Атрибути полів',
|
||||||
unique: 'Унікальне',
|
unique: 'Унікальне',
|
||||||
|
auto_increment: 'Автоінкремент',
|
||||||
comments: 'Коментарі',
|
comments: 'Коментарі',
|
||||||
no_comments: 'Немає коментарів',
|
no_comments: 'Немає коментарів',
|
||||||
delete_field: 'Видалити поле',
|
delete_field: 'Видалити поле',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Точність',
|
||||||
|
scale: 'Масштаб',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Атрибути індексу',
|
title: 'Атрибути індексу',
|
||||||
name: 'Назва індекса',
|
name: 'Назва індекса',
|
||||||
unique: 'Унікальний',
|
unique: 'Унікальний',
|
||||||
|
index_type: 'Тип індексу',
|
||||||
delete_index: 'Видалити індекс',
|
delete_index: 'Видалити індекс',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -171,12 +177,15 @@ export const uk: LanguageTranslation = {
|
|||||||
description: 'Щоб почати, створіть таблицю',
|
description: 'Щоб почати, створіть таблицю',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Звʼязки',
|
refs: 'Refs',
|
||||||
filter: 'Фільтр',
|
filter: 'Фільтр',
|
||||||
add_relationship: 'Додати звʼязок',
|
|
||||||
collapse: 'Згорнути все',
|
collapse: 'Згорнути все',
|
||||||
|
add_relationship: 'Додати звʼязок',
|
||||||
|
relationships: 'Звʼязки',
|
||||||
|
dependencies: 'Залежності',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Звʼязок',
|
||||||
primary: 'Первинна таблиця',
|
primary: 'Первинна таблиця',
|
||||||
foreign: 'Посилання на таблицю',
|
foreign: 'Посилання на таблицю',
|
||||||
cardinality: 'Звʼязок',
|
cardinality: 'Звʼязок',
|
||||||
@@ -186,16 +195,8 @@ export const uk: LanguageTranslation = {
|
|||||||
delete_relationship: 'Видалити',
|
delete_relationship: 'Видалити',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'Звʼязків немає',
|
|
||||||
description: 'Створіть звʼязок для зʼєднання таблиць',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Залежності',
|
|
||||||
filter: 'Фільтр',
|
|
||||||
collapse: 'Згорнути все',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Залежність',
|
||||||
table: 'Таблиця',
|
table: 'Таблиця',
|
||||||
dependent_table: 'Залежне подання',
|
dependent_table: 'Залежне подання',
|
||||||
delete_dependency: 'Видалити',
|
delete_dependency: 'Видалити',
|
||||||
@@ -205,8 +206,8 @@ export const uk: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Жодних залежностей',
|
title: 'Жодних зв’язків',
|
||||||
description: 'Створіть подання, щоб почати',
|
description: 'Створіть зв’язок, щоб почати',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -230,6 +231,35 @@ export const uk: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Візуальні елементи',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Нотатки',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Фільтр',
|
||||||
|
add_note: 'Додати Нотатку',
|
||||||
|
no_results: 'Нотатки не знайдено',
|
||||||
|
clear: 'Очистити Фільтр',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Немає Нотаток',
|
||||||
|
description:
|
||||||
|
'Створіть нотатку, щоб додати текстові анотації на полотні',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Порожня нотатка',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Дії з Нотаткою',
|
||||||
|
edit_content: 'Редагувати Вміст',
|
||||||
|
delete_note: 'Видалити Нотатку',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -246,12 +276,16 @@ export const uk: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'Значення переліку не визначені',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -265,8 +299,14 @@ export const uk: LanguageTranslation = {
|
|||||||
show_all: 'Показати все',
|
show_all: 'Показати все',
|
||||||
undo: 'Скасувати',
|
undo: 'Скасувати',
|
||||||
redo: 'Повторити',
|
redo: 'Повторити',
|
||||||
reorder_diagram: 'Перевпорядкувати діаграму',
|
reorder_diagram: 'Автоматичне розміщення діаграми',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Показати таблиці, що перекриваються',
|
highlight_overlapping_tables: 'Показати таблиці, що перекриваються',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -297,13 +337,13 @@ export const uk: LanguageTranslation = {
|
|||||||
cancel: 'Скасувати',
|
cancel: 'Скасувати',
|
||||||
back: 'Назад',
|
back: 'Назад',
|
||||||
import_from_file: 'Імпортувати з файлу',
|
import_from_file: 'Імпортувати з файлу',
|
||||||
empty_diagram: 'Порожня діаграма',
|
empty_diagram: 'Порожня база даних',
|
||||||
continue: 'Продовжити',
|
continue: 'Продовжити',
|
||||||
import: 'Імпорт',
|
import: 'Імпорт',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Відкрити діаграму',
|
title: 'Відкрити базу даних',
|
||||||
description:
|
description:
|
||||||
'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.',
|
'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
@@ -314,6 +354,12 @@ export const uk: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Скасувати',
|
cancel: 'Скасувати',
|
||||||
open: 'Відкрити',
|
open: 'Відкрити',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Відкрити',
|
||||||
|
duplicate: 'Дублювати',
|
||||||
|
delete: 'Видалити',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -400,6 +446,14 @@ export const uk: LanguageTranslation = {
|
|||||||
confirm: 'Змінити',
|
confirm: 'Змінити',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Створити нову схему',
|
||||||
|
description:
|
||||||
|
'Поки що не існує жодної схеми. Створіть свою першу схему, щоб організувати ваші таблиці.',
|
||||||
|
create: 'Створити',
|
||||||
|
cancel: 'Скасувати',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Допоможіть нам покращитися!',
|
title: 'Допоможіть нам покращитися!',
|
||||||
description: 'Поставне на зірку на GitHub? Це лише один клік!',
|
description: 'Поставне на зірку на GitHub? Це лише один клік!',
|
||||||
@@ -452,9 +506,11 @@ export const uk: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Нова таблиця',
|
new_table: 'Нова таблиця',
|
||||||
|
new_view: 'Нове представлення',
|
||||||
new_relationship: 'Новий звʼязок',
|
new_relationship: 'Новий звʼязок',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Нова Нотатка',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -473,6 +529,9 @@ export const uk: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Мова',
|
change_language: 'Мова',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Увімк',
|
||||||
|
off: 'Вимк',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const vi: LanguageTranslation = {
|
export const vi: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: 'Mới',
|
||||||
|
browse: 'Duyệt',
|
||||||
|
tables: 'Bảng',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: 'Phụ thuộc',
|
||||||
|
custom_types: 'Kiểu tùy chỉnh',
|
||||||
|
visuals: 'Hình ảnh',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: 'Tệp',
|
actions: 'Hành động',
|
||||||
new: 'Tạo mới',
|
new: 'Mới...',
|
||||||
open: 'Mở',
|
browse: 'Duyệt...',
|
||||||
save: 'Lưu',
|
save: 'Lưu',
|
||||||
import: 'Nhập cơ sở dữ liệu',
|
import: 'Nhập cơ sở dữ liệu',
|
||||||
export_sql: 'Xuất SQL',
|
export_sql: 'Xuất SQL',
|
||||||
export_as: 'Xuất thành',
|
export_as: 'Xuất thành',
|
||||||
delete_diagram: 'Xóa sơ đồ',
|
delete_diagram: 'Xóa',
|
||||||
exit: 'Thoát',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: 'Sửa',
|
edit: 'Sửa',
|
||||||
@@ -26,7 +34,10 @@ export const vi: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Ẩn thanh bên',
|
hide_sidebar: 'Ẩn thanh bên',
|
||||||
hide_cardinality: 'Ẩn số lượng',
|
hide_cardinality: 'Ẩn số lượng',
|
||||||
show_cardinality: 'Hiển thị số lượng',
|
show_cardinality: 'Hiển thị số lượng',
|
||||||
|
show_field_attributes: 'Hiển thị thuộc tính trường',
|
||||||
|
hide_field_attributes: 'Ẩn thuộc tính trường',
|
||||||
zoom_on_scroll: 'Thu phóng khi cuộn',
|
zoom_on_scroll: 'Thu phóng khi cuộn',
|
||||||
|
show_views: 'Chế độ xem Cơ sở dữ liệu',
|
||||||
theme: 'Chủ đề',
|
theme: 'Chủ đề',
|
||||||
show_dependencies: 'Hiển thị các phụ thuộc',
|
show_dependencies: 'Hiển thị các phụ thuộc',
|
||||||
hide_dependencies: 'Ẩn các phụ thuộc',
|
hide_dependencies: 'Ẩn các phụ thuộc',
|
||||||
@@ -63,22 +74,13 @@ export const vi: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: 'Sắp xếp lại sơ đồ',
|
title: 'Tự động sắp xếp sơ đồ',
|
||||||
description:
|
description:
|
||||||
'Hành động này sẽ sắp xếp lại tất cả các bảng trong sơ đồ. Bạn có muốn tiếp tục không?',
|
'Hành động này sẽ sắp xếp lại tất cả các bảng trong sơ đồ. Bạn có muốn tiếp tục không?',
|
||||||
reorder: 'Sắp xếp',
|
reorder: 'Tự động sắp xếp',
|
||||||
cancel: 'Hủy',
|
cancel: 'Hủy',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: 'Có nhiều lược đồ',
|
|
||||||
description:
|
|
||||||
'Có {{schemasCount}} lược đồ trong sơ đồ này. Hiện đang hiển thị: {{formattedSchemas}}.',
|
|
||||||
dont_show_again: 'Không hiển thị lại',
|
|
||||||
change_schema: 'Thay đổi',
|
|
||||||
none: 'không có',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: 'Sao chép thất bại',
|
title: 'Sao chép thất bại',
|
||||||
@@ -113,14 +115,11 @@ export const vi: LanguageTranslation = {
|
|||||||
copied: 'Đã sao chép!',
|
copied: 'Đã sao chép!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Lược đồ:',
|
|
||||||
filter_by_schema: 'Lọc bởi lược đồ',
|
|
||||||
search_schema: 'Tìm kiếm lược đồ...',
|
|
||||||
no_schemas_found: 'Không tìm thấy lược đồ.',
|
|
||||||
view_all_options: 'Xem tất cả tùy chọn...',
|
view_all_options: 'Xem tất cả tùy chọn...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: 'Bảng',
|
tables: 'Bảng',
|
||||||
add_table: 'Thêm bảng',
|
add_table: 'Thêm bảng',
|
||||||
|
add_view: 'Thêm Chế độ xem',
|
||||||
filter: 'Lọc',
|
filter: 'Lọc',
|
||||||
collapse: 'Thu gọn tất cả',
|
collapse: 'Thu gọn tất cả',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -146,16 +145,23 @@ export const vi: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: 'Thuộc tính trường',
|
title: 'Thuộc tính trường',
|
||||||
unique: 'Giá trị duy nhất',
|
unique: 'Giá trị duy nhất',
|
||||||
|
auto_increment: 'Tự động tăng',
|
||||||
comments: 'Bình luận',
|
comments: 'Bình luận',
|
||||||
no_comments: 'Không có bình luận',
|
no_comments: 'Không có bình luận',
|
||||||
delete_field: 'Xóa trường',
|
delete_field: 'Xóa trường',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: 'Độ chính xác',
|
||||||
|
scale: 'Tỷ lệ',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: 'Thuộc tính chỉ mục',
|
title: 'Thuộc tính chỉ mục',
|
||||||
name: 'Tên',
|
name: 'Tên',
|
||||||
unique: 'Giá trị duy nhất',
|
unique: 'Giá trị duy nhất',
|
||||||
|
index_type: 'Loại chỉ mục',
|
||||||
delete_index: 'Xóa chỉ mục',
|
delete_index: 'Xóa chỉ mục',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -172,12 +178,15 @@ export const vi: LanguageTranslation = {
|
|||||||
description: 'Tạo một bảng để bắt đầu',
|
description: 'Tạo một bảng để bắt đầu',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: 'Quan hệ',
|
refs: 'Refs',
|
||||||
filter: 'Lọc',
|
filter: 'Lọc',
|
||||||
add_relationship: 'Thêm quan hệ',
|
|
||||||
collapse: 'Thu gọn tất cả',
|
collapse: 'Thu gọn tất cả',
|
||||||
|
add_relationship: 'Thêm quan hệ',
|
||||||
|
relationships: 'Quan hệ',
|
||||||
|
dependencies: 'Phụ thuộc',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: 'Quan hệ',
|
||||||
primary: 'Bảng khóa chính',
|
primary: 'Bảng khóa chính',
|
||||||
foreign: 'Bảng khóa ngoại',
|
foreign: 'Bảng khóa ngoại',
|
||||||
cardinality: 'Quan hệ',
|
cardinality: 'Quan hệ',
|
||||||
@@ -187,16 +196,8 @@ export const vi: LanguageTranslation = {
|
|||||||
delete_relationship: 'Xóa',
|
delete_relationship: 'Xóa',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: 'Không có quan hệ',
|
|
||||||
description: 'Tạo quan hệ để kết nối các bảng',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: 'Phụ thuộc',
|
|
||||||
filter: 'Lọc',
|
|
||||||
collapse: 'Thu gọn tất cả',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: 'Phụ thuộc',
|
||||||
table: 'Bảng',
|
table: 'Bảng',
|
||||||
dependent_table: 'Bảng xem phụ thuộc',
|
dependent_table: 'Bảng xem phụ thuộc',
|
||||||
delete_dependency: 'Xóa',
|
delete_dependency: 'Xóa',
|
||||||
@@ -206,8 +207,8 @@ export const vi: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: 'Không có phụ thuộc',
|
title: 'Không có quan hệ',
|
||||||
description: 'Tạo bảng xem phụ thuộc để bắt đầu',
|
description: 'Tạo một quan hệ để bắt đầu',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -231,6 +232,35 @@ export const vi: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: 'Hình ảnh',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: 'Ghi chú',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: 'Lọc',
|
||||||
|
add_note: 'Thêm Ghi Chú',
|
||||||
|
no_results: 'Không tìm thấy ghi chú',
|
||||||
|
clear: 'Xóa Bộ Lọc',
|
||||||
|
empty_state: {
|
||||||
|
title: 'Không Có Ghi Chú',
|
||||||
|
description:
|
||||||
|
'Tạo ghi chú để thêm chú thích văn bản trên canvas',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: 'Ghi chú trống',
|
||||||
|
note_actions: {
|
||||||
|
title: 'Hành Động Ghi Chú',
|
||||||
|
edit_content: 'Chỉnh Sửa Nội Dung',
|
||||||
|
delete_note: 'Xóa Ghi Chú',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -247,12 +277,16 @@ export const vi: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: 'Không có giá trị enum được định nghĩa',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -266,8 +300,14 @@ export const vi: LanguageTranslation = {
|
|||||||
show_all: 'Hiển thị tất cả',
|
show_all: 'Hiển thị tất cả',
|
||||||
undo: 'Hoàn tác',
|
undo: 'Hoàn tác',
|
||||||
redo: 'Làm lại',
|
redo: 'Làm lại',
|
||||||
reorder_diagram: 'Sắp xếp lại sơ đồ',
|
reorder_diagram: 'Tự động sắp xếp sơ đồ',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: 'Làm nổi bật các bảng chồng chéo',
|
highlight_overlapping_tables: 'Làm nổi bật các bảng chồng chéo',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -298,13 +338,13 @@ export const vi: LanguageTranslation = {
|
|||||||
cancel: 'Hủy',
|
cancel: 'Hủy',
|
||||||
import_from_file: 'Nhập từ tệp',
|
import_from_file: 'Nhập từ tệp',
|
||||||
back: 'Trở lại',
|
back: 'Trở lại',
|
||||||
empty_diagram: 'Sơ đồ trống',
|
empty_diagram: 'Cơ sở dữ liệu trống',
|
||||||
continue: 'Tiếp tục',
|
continue: 'Tiếp tục',
|
||||||
import: 'Nhập',
|
import: 'Nhập',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: 'Mở sơ đồ',
|
title: 'Mở cơ sở dữ liệu',
|
||||||
description: 'Chọn sơ đồ để mở từ danh sách bên dưới.',
|
description: 'Chọn sơ đồ để mở từ danh sách bên dưới.',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: 'Tên',
|
name: 'Tên',
|
||||||
@@ -314,6 +354,12 @@ export const vi: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: 'Hủy',
|
cancel: 'Hủy',
|
||||||
open: 'Mở',
|
open: 'Mở',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: 'Mở',
|
||||||
|
duplicate: 'Nhân bản',
|
||||||
|
delete: 'Xóa',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -399,6 +445,14 @@ export const vi: LanguageTranslation = {
|
|||||||
confirm: 'Xác nhận',
|
confirm: 'Xác nhận',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: 'Tạo lược đồ mới',
|
||||||
|
description:
|
||||||
|
'Chưa có lược đồ nào. Tạo lược đồ đầu tiên của bạn để tổ chức các bảng.',
|
||||||
|
create: 'Tạo',
|
||||||
|
cancel: 'Hủy',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: 'Hãy giúp chúng tôi cải thiện!',
|
title: 'Hãy giúp chúng tôi cải thiện!',
|
||||||
description:
|
description:
|
||||||
@@ -453,9 +507,11 @@ export const vi: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: 'Tạo bảng mới',
|
new_table: 'Tạo bảng mới',
|
||||||
|
new_view: 'Chế độ xem Mới',
|
||||||
new_relationship: 'Tạo quan hệ mới',
|
new_relationship: 'Tạo quan hệ mới',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: 'Ghi Chú Mới',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -474,6 +530,9 @@ export const vi: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: 'Ngôn ngữ',
|
change_language: 'Ngôn ngữ',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: 'Bật',
|
||||||
|
off: 'Tắt',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const zh_CN: LanguageTranslation = {
|
export const zh_CN: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: '新建',
|
||||||
|
browse: '浏览',
|
||||||
|
tables: '表',
|
||||||
|
refs: '引用',
|
||||||
|
dependencies: '依赖关系',
|
||||||
|
custom_types: '自定义类型',
|
||||||
|
visuals: '视觉效果',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: '文件',
|
actions: '操作',
|
||||||
new: '新建',
|
new: '新建...',
|
||||||
open: '打开',
|
browse: '浏览...',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
import: '导入数据库',
|
import: '导入数据库',
|
||||||
export_sql: '导出 SQL 语句',
|
export_sql: '导出 SQL 语句',
|
||||||
export_as: '导出为',
|
export_as: '导出为',
|
||||||
delete_diagram: '删除关系图',
|
delete_diagram: '删除',
|
||||||
exit: '退出',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
@@ -26,7 +34,10 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
hide_sidebar: '隐藏侧边栏',
|
hide_sidebar: '隐藏侧边栏',
|
||||||
hide_cardinality: '隐藏基数',
|
hide_cardinality: '隐藏基数',
|
||||||
show_cardinality: '展示基数',
|
show_cardinality: '展示基数',
|
||||||
|
show_field_attributes: '展示字段属性',
|
||||||
|
hide_field_attributes: '隐藏字段属性',
|
||||||
zoom_on_scroll: '滚动缩放',
|
zoom_on_scroll: '滚动缩放',
|
||||||
|
show_views: '数据库视图',
|
||||||
theme: '主题',
|
theme: '主题',
|
||||||
show_dependencies: '展示依赖',
|
show_dependencies: '展示依赖',
|
||||||
hide_dependencies: '隐藏依赖',
|
hide_dependencies: '隐藏依赖',
|
||||||
@@ -61,21 +72,12 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: '重新排列关系图',
|
title: '自动排列关系图',
|
||||||
description: '此操作将重新排列关系图中的所有表。是否要继续?',
|
description: '此操作将重新排列关系图中的所有表。是否要继续?',
|
||||||
reorder: '重新排列',
|
reorder: '自动排列',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '多个模式',
|
|
||||||
description:
|
|
||||||
'此关系图中有 {{schemasCount}} 个模式,当前显示:{{formattedSchemas}}。',
|
|
||||||
dont_show_again: '不再展示',
|
|
||||||
change_schema: '更改',
|
|
||||||
none: '无',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: '复制失败',
|
title: '复制失败',
|
||||||
@@ -110,14 +112,11 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
copied: '复制了!',
|
copied: '复制了!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: '模式:',
|
|
||||||
filter_by_schema: '按模式筛选',
|
|
||||||
search_schema: '搜索模式...',
|
|
||||||
no_schemas_found: '未找到模式。',
|
|
||||||
view_all_options: '查看所有选项...',
|
view_all_options: '查看所有选项...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: '表',
|
tables: '表',
|
||||||
add_table: '添加表',
|
add_table: '添加表',
|
||||||
|
add_view: '添加视图',
|
||||||
filter: '筛选',
|
filter: '筛选',
|
||||||
collapse: '全部折叠',
|
collapse: '全部折叠',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -143,16 +142,23 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: '字段属性',
|
title: '字段属性',
|
||||||
unique: '唯一',
|
unique: '唯一',
|
||||||
|
auto_increment: '自动递增',
|
||||||
comments: '注释',
|
comments: '注释',
|
||||||
no_comments: '空',
|
no_comments: '空',
|
||||||
delete_field: '删除字段',
|
delete_field: '删除字段',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: '精度',
|
||||||
|
scale: '小数位',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: '索引属性',
|
title: '索引属性',
|
||||||
name: '名称',
|
name: '名称',
|
||||||
unique: '唯一',
|
unique: '唯一',
|
||||||
|
index_type: '索引类型',
|
||||||
delete_index: '删除索引',
|
delete_index: '删除索引',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -169,12 +175,15 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
description: '新建表以开始',
|
description: '新建表以开始',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: '关系',
|
refs: '引用',
|
||||||
filter: '筛选',
|
filter: '筛选',
|
||||||
add_relationship: '添加关系',
|
|
||||||
collapse: '全部折叠',
|
collapse: '全部折叠',
|
||||||
|
add_relationship: '添加关系',
|
||||||
|
relationships: '关系',
|
||||||
|
dependencies: '依赖关系',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: '关系',
|
||||||
primary: '主表',
|
primary: '主表',
|
||||||
foreign: '被引用表',
|
foreign: '被引用表',
|
||||||
cardinality: '基数',
|
cardinality: '基数',
|
||||||
@@ -184,16 +193,8 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
delete_relationship: '删除',
|
delete_relationship: '删除',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: '无关系',
|
|
||||||
description: '创建关系以连接表',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: '依赖关系',
|
|
||||||
filter: '筛选',
|
|
||||||
collapse: '全部折叠',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: '依赖',
|
||||||
table: '表',
|
table: '表',
|
||||||
dependent_table: '依赖视图',
|
dependent_table: '依赖视图',
|
||||||
delete_dependency: '删除',
|
delete_dependency: '删除',
|
||||||
@@ -203,8 +204,8 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: '无依赖',
|
title: '无关系',
|
||||||
description: '创建视图以开始',
|
description: '创建关系以开始',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -228,6 +229,34 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: '视觉效果',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: '笔记',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: '筛选',
|
||||||
|
add_note: '添加笔记',
|
||||||
|
no_results: '未找到笔记',
|
||||||
|
clear: '清除筛选',
|
||||||
|
empty_state: {
|
||||||
|
title: '没有笔记',
|
||||||
|
description: '创建笔记以在画布上添加文本注释',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: '空笔记',
|
||||||
|
note_actions: {
|
||||||
|
title: '笔记操作',
|
||||||
|
edit_content: '编辑内容',
|
||||||
|
delete_note: '删除笔记',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -244,12 +273,16 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: '没有定义枚举值',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -263,8 +296,14 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
show_all: '展示全部',
|
show_all: '展示全部',
|
||||||
undo: '撤销',
|
undo: '撤销',
|
||||||
redo: '重做',
|
redo: '重做',
|
||||||
reorder_diagram: '重新排列关系图',
|
reorder_diagram: '自动排列关系图',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: '突出显示重叠的表',
|
highlight_overlapping_tables: '突出显示重叠的表',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -295,13 +334,13 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
import_from_file: '从文件导入',
|
import_from_file: '从文件导入',
|
||||||
back: '上一步',
|
back: '上一步',
|
||||||
empty_diagram: '新建空关系图',
|
empty_diagram: '空数据库',
|
||||||
continue: '下一步',
|
continue: '下一步',
|
||||||
import: '导入',
|
import: '导入',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: '打开关系图',
|
title: '打开数据库',
|
||||||
description: '从下面的列表中选择一个图表打开。',
|
description: '从下面的列表中选择一个图表打开。',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: '名称',
|
name: '名称',
|
||||||
@@ -311,6 +350,12 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
open: '打开',
|
open: '打开',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: '打开',
|
||||||
|
duplicate: '复制',
|
||||||
|
delete: '删除',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -395,6 +440,13 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
confirm: '更改',
|
confirm: '更改',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: '创建新模式',
|
||||||
|
description: '尚未存在任何模式。创建您的第一个模式来组织您的表。',
|
||||||
|
create: '创建',
|
||||||
|
cancel: '取消',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: '帮助我们改进!',
|
title: '帮助我们改进!',
|
||||||
description: '您想在 GitHub 上为我们加注星标吗?只需点击一下即可!',
|
description: '您想在 GitHub 上为我们加注星标吗?只需点击一下即可!',
|
||||||
@@ -449,9 +501,11 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '新建表',
|
new_table: '新建表',
|
||||||
|
new_view: '新建视图',
|
||||||
new_relationship: '新建关系',
|
new_relationship: '新建关系',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: '新笔记',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -470,6 +524,9 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: '语言',
|
change_language: '语言',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: '开启',
|
||||||
|
off: '关闭',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
|||||||
|
|
||||||
export const zh_TW: LanguageTranslation = {
|
export const zh_TW: LanguageTranslation = {
|
||||||
translation: {
|
translation: {
|
||||||
|
editor_sidebar: {
|
||||||
|
new_diagram: '新建',
|
||||||
|
browse: '瀏覽',
|
||||||
|
tables: '表格',
|
||||||
|
refs: 'Refs',
|
||||||
|
dependencies: '相依性',
|
||||||
|
custom_types: '自定義類型',
|
||||||
|
visuals: '視覺效果',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
file: {
|
actions: {
|
||||||
file: '檔案',
|
actions: '操作',
|
||||||
new: '新增',
|
new: '新增...',
|
||||||
open: '開啟',
|
browse: '瀏覽...',
|
||||||
save: '儲存',
|
save: '儲存',
|
||||||
import: '匯入資料庫',
|
import: '匯入資料庫',
|
||||||
export_sql: '匯出 SQL',
|
export_sql: '匯出 SQL',
|
||||||
export_as: '匯出為特定格式',
|
export_as: '匯出為特定格式',
|
||||||
delete_diagram: '刪除圖表',
|
delete_diagram: '刪除',
|
||||||
exit: '退出',
|
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
edit: '編輯',
|
edit: '編輯',
|
||||||
@@ -26,7 +34,10 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
hide_sidebar: '隱藏側邊欄',
|
hide_sidebar: '隱藏側邊欄',
|
||||||
hide_cardinality: '隱藏基數',
|
hide_cardinality: '隱藏基數',
|
||||||
show_cardinality: '顯示基數',
|
show_cardinality: '顯示基數',
|
||||||
|
hide_field_attributes: '隱藏欄位屬性',
|
||||||
|
show_field_attributes: '顯示欄位屬性',
|
||||||
zoom_on_scroll: '滾動縮放',
|
zoom_on_scroll: '滾動縮放',
|
||||||
|
show_views: '資料庫檢視',
|
||||||
theme: '主題',
|
theme: '主題',
|
||||||
show_dependencies: '顯示相依性',
|
show_dependencies: '顯示相依性',
|
||||||
hide_dependencies: '隱藏相依性',
|
hide_dependencies: '隱藏相依性',
|
||||||
@@ -61,21 +72,12 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reorder_diagram_alert: {
|
reorder_diagram_alert: {
|
||||||
title: '重新排列圖表',
|
title: '自動排列圖表',
|
||||||
description: '此操作將重新排列圖表中的所有表格。是否繼續?',
|
description: '此操作將重新排列圖表中的所有表格。是否繼續?',
|
||||||
reorder: '重新排列',
|
reorder: '自動排列',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple_schemas_alert: {
|
|
||||||
title: '多重 Schema',
|
|
||||||
description:
|
|
||||||
'此圖表中包含 {{schemasCount}} 個 Schema,目前顯示:{{formattedSchemas}}。',
|
|
||||||
dont_show_again: '不再顯示',
|
|
||||||
change_schema: '變更',
|
|
||||||
none: '無',
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_to_clipboard_toast: {
|
copy_to_clipboard_toast: {
|
||||||
unsupported: {
|
unsupported: {
|
||||||
title: '複製失敗',
|
title: '複製失敗',
|
||||||
@@ -110,14 +112,11 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
copied: '已複製!',
|
copied: '已複製!',
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schema:',
|
|
||||||
filter_by_schema: '依 Schema 篩選',
|
|
||||||
search_schema: '搜尋 Schema...',
|
|
||||||
no_schemas_found: '未找到 Schema。',
|
|
||||||
view_all_options: '顯示所有選項...',
|
view_all_options: '顯示所有選項...',
|
||||||
tables_section: {
|
tables_section: {
|
||||||
tables: '表格',
|
tables: '表格',
|
||||||
add_table: '新增表格',
|
add_table: '新增表格',
|
||||||
|
add_view: '新增檢視',
|
||||||
filter: '篩選',
|
filter: '篩選',
|
||||||
collapse: '全部摺疊',
|
collapse: '全部摺疊',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
@@ -143,16 +142,23 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
field_actions: {
|
field_actions: {
|
||||||
title: '欄位屬性',
|
title: '欄位屬性',
|
||||||
unique: '唯一',
|
unique: '唯一',
|
||||||
|
auto_increment: '自動遞增',
|
||||||
comments: '註解',
|
comments: '註解',
|
||||||
no_comments: '無註解',
|
no_comments: '無註解',
|
||||||
delete_field: '刪除欄位',
|
delete_field: '刪除欄位',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
default_value: 'Default Value',
|
||||||
|
no_default: 'No default',
|
||||||
|
// TODO: Translate
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
|
precision: '精度',
|
||||||
|
scale: '小數位',
|
||||||
},
|
},
|
||||||
index_actions: {
|
index_actions: {
|
||||||
title: '索引屬性',
|
title: '索引屬性',
|
||||||
name: '名稱',
|
name: '名稱',
|
||||||
unique: '唯一',
|
unique: '唯一',
|
||||||
|
index_type: '索引類型',
|
||||||
delete_index: '刪除索引',
|
delete_index: '刪除索引',
|
||||||
},
|
},
|
||||||
table_actions: {
|
table_actions: {
|
||||||
@@ -169,12 +175,15 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
description: '請新增表格以開始',
|
description: '請新增表格以開始',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationships_section: {
|
refs_section: {
|
||||||
relationships: '關聯',
|
refs: 'Refs',
|
||||||
filter: '篩選',
|
filter: '篩選',
|
||||||
add_relationship: '新增關聯',
|
|
||||||
collapse: '全部摺疊',
|
collapse: '全部摺疊',
|
||||||
|
add_relationship: '新增關聯',
|
||||||
|
relationships: '關聯',
|
||||||
|
dependencies: '相依性',
|
||||||
relationship: {
|
relationship: {
|
||||||
|
relationship: '關聯',
|
||||||
primary: '主表格',
|
primary: '主表格',
|
||||||
foreign: '參照表格',
|
foreign: '參照表格',
|
||||||
cardinality: '基數',
|
cardinality: '基數',
|
||||||
@@ -184,16 +193,8 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
delete_relationship: '刪除',
|
delete_relationship: '刪除',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
|
||||||
title: '尚無關聯',
|
|
||||||
description: '請新增關聯以連接表格',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies_section: {
|
|
||||||
dependencies: '相依性',
|
|
||||||
filter: '篩選',
|
|
||||||
collapse: '全部摺疊',
|
|
||||||
dependency: {
|
dependency: {
|
||||||
|
dependency: '相依性',
|
||||||
table: '表格',
|
table: '表格',
|
||||||
dependent_table: '相依檢視',
|
dependent_table: '相依檢視',
|
||||||
delete_dependency: '刪除',
|
delete_dependency: '刪除',
|
||||||
@@ -203,8 +204,8 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
empty_state: {
|
empty_state: {
|
||||||
title: '尚無相依性',
|
title: '尚無關聯',
|
||||||
description: '請建立檢視以開始',
|
description: '請建立關聯以開始',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -228,6 +229,34 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
description: 'Create an area to get started',
|
description: 'Create an area to get started',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visuals_section: {
|
||||||
|
visuals: '視覺效果',
|
||||||
|
tabs: {
|
||||||
|
areas: 'Areas',
|
||||||
|
notes: '筆記',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notes_section: {
|
||||||
|
filter: '篩選',
|
||||||
|
add_note: '新增筆記',
|
||||||
|
no_results: '未找到筆記',
|
||||||
|
clear: '清除篩選',
|
||||||
|
empty_state: {
|
||||||
|
title: '沒有筆記',
|
||||||
|
description: '建立筆記以在畫布上新增文字註解',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
empty_note: '空白筆記',
|
||||||
|
note_actions: {
|
||||||
|
title: '筆記操作',
|
||||||
|
edit_content: '編輯內容',
|
||||||
|
delete_note: '刪除筆記',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
custom_types_section: {
|
custom_types_section: {
|
||||||
custom_types: 'Custom Types',
|
custom_types: 'Custom Types',
|
||||||
@@ -244,12 +273,16 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
enum_values: 'Enum Values',
|
enum_values: 'Enum Values',
|
||||||
composite_fields: 'Fields',
|
composite_fields: 'Fields',
|
||||||
no_fields: 'No fields defined',
|
no_fields: 'No fields defined',
|
||||||
|
no_values: '沒有定義列舉值',
|
||||||
field_name_placeholder: 'Field name',
|
field_name_placeholder: 'Field name',
|
||||||
field_type_placeholder: 'Select type',
|
field_type_placeholder: 'Select type',
|
||||||
add_field: 'Add Field',
|
add_field: 'Add Field',
|
||||||
|
no_fields_tooltip: 'No fields defined for this custom type',
|
||||||
custom_type_actions: {
|
custom_type_actions: {
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
highlight_fields: 'Highlight Fields',
|
||||||
delete_custom_type: 'Delete',
|
delete_custom_type: 'Delete',
|
||||||
|
clear_field_highlight: 'Clear Highlight',
|
||||||
},
|
},
|
||||||
delete_custom_type: 'Delete Type',
|
delete_custom_type: 'Delete Type',
|
||||||
},
|
},
|
||||||
@@ -263,8 +296,14 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
show_all: '顯示全部',
|
show_all: '顯示全部',
|
||||||
undo: '復原',
|
undo: '復原',
|
||||||
redo: '重做',
|
redo: '重做',
|
||||||
reorder_diagram: '重新排列圖表',
|
reorder_diagram: '自動排列圖表',
|
||||||
|
// TODO: Translate
|
||||||
|
clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
|
||||||
|
custom_type_highlight_tooltip:
|
||||||
|
'Highlighting "{{typeName}}" - Click to clear',
|
||||||
highlight_overlapping_tables: '突出顯示重疊表格',
|
highlight_overlapping_tables: '突出顯示重疊表格',
|
||||||
|
// TODO: Translate
|
||||||
|
filter: 'Filter Tables',
|
||||||
},
|
},
|
||||||
|
|
||||||
new_diagram_dialog: {
|
new_diagram_dialog: {
|
||||||
@@ -294,13 +333,13 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
import_from_file: '從檔案匯入',
|
import_from_file: '從檔案匯入',
|
||||||
back: '返回',
|
back: '返回',
|
||||||
empty_diagram: '空白圖表',
|
empty_diagram: '空資料庫',
|
||||||
continue: '繼續',
|
continue: '繼續',
|
||||||
import: '匯入',
|
import: '匯入',
|
||||||
},
|
},
|
||||||
|
|
||||||
open_diagram_dialog: {
|
open_diagram_dialog: {
|
||||||
title: '開啟圖表',
|
title: '開啟資料庫',
|
||||||
description: '請從以下列表中選擇一個圖表。',
|
description: '請從以下列表中選擇一個圖表。',
|
||||||
table_columns: {
|
table_columns: {
|
||||||
name: '名稱',
|
name: '名稱',
|
||||||
@@ -310,6 +349,12 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
open: '開啟',
|
open: '開啟',
|
||||||
|
|
||||||
|
diagram_actions: {
|
||||||
|
open: '開啟',
|
||||||
|
duplicate: '複製',
|
||||||
|
delete: '刪除',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
export_sql_dialog: {
|
export_sql_dialog: {
|
||||||
@@ -394,6 +439,14 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
confirm: '變更',
|
confirm: '變更',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_table_schema_dialog: {
|
||||||
|
title: '建立新 Schema',
|
||||||
|
description:
|
||||||
|
'尚未存在任何 Schema。建立您的第一個 Schema 來組織您的表格。',
|
||||||
|
create: '建立',
|
||||||
|
cancel: '取消',
|
||||||
|
},
|
||||||
|
|
||||||
star_us_dialog: {
|
star_us_dialog: {
|
||||||
title: '協助我們改善!',
|
title: '協助我們改善!',
|
||||||
description: '請在 GitHub 上給我們一顆星,只需點擊一下!',
|
description: '請在 GitHub 上給我們一顆星,只需點擊一下!',
|
||||||
@@ -448,9 +501,11 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
|
|
||||||
canvas_context_menu: {
|
canvas_context_menu: {
|
||||||
new_table: '新建表格',
|
new_table: '新建表格',
|
||||||
|
new_view: '新檢視',
|
||||||
new_relationship: '新建關聯',
|
new_relationship: '新建關聯',
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
new_area: 'New Area',
|
new_area: 'New Area',
|
||||||
|
new_note: '新筆記',
|
||||||
},
|
},
|
||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
@@ -469,6 +524,9 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
language_select: {
|
language_select: {
|
||||||
change_language: '變更語言',
|
change_language: '變更語言',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on: '開啟',
|
||||||
|
off: '關閉',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user