mirror of
https://github.com/chartdb/chartdb.git
synced 2026-01-05 11:19:57 -06:00
add relationship from side panel
This commit is contained in:
committed by
Guy Ben-Aharon
parent
fa346ae607
commit
ffbbc3af40
592
package-lock.json
generated
592
package-lock.json
generated
@@ -43,7 +43,7 @@
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"html-to-image": "^1.11.11",
|
||||
"i18next": "^23.14.0",
|
||||
"lucide-react": "^0.424.0",
|
||||
"lucide-react": "^0.441.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
@@ -692,91 +692,6 @@
|
||||
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||
@@ -794,295 +709,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||
@@ -2586,48 +2212,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
|
||||
"integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz",
|
||||
"integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
|
||||
"integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz",
|
||||
@@ -2642,174 +2226,6 @@
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz",
|
||||
"integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz",
|
||||
"integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz",
|
||||
"integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz",
|
||||
"integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz",
|
||||
"integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz",
|
||||
"integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz",
|
||||
"integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
@@ -7332,9 +6748,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.424.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.424.0.tgz",
|
||||
"integrity": "sha512-x2Nj2aytk1iOyHqt4hKenfVlySq0rYxNeEf8hE0o+Yh0iE36Rqz0rkngVdv2uQtjZ70LAE73eeplhhptYt9x4Q==",
|
||||
"version": "0.441.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.441.0.tgz",
|
||||
"integrity": "sha512-0vfExYtvSDhkC2lqg0zYVW1Uu9GsI4knuV9GP9by5z0Xhc4Zi5RejTxfz9LsjRmCyWVzHCJvxGKZWcRyvQCWVg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"html-to-image": "^1.11.11",
|
||||
"i18next": "^23.14.0",
|
||||
"lucide-react": "^0.424.0",
|
||||
"lucide-react": "^0.441.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
|
||||
@@ -40,6 +40,7 @@ interface SelectBoxProps {
|
||||
clearText?: string;
|
||||
showClear?: boolean;
|
||||
keepOrder?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
@@ -59,6 +60,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
clearText,
|
||||
showClear,
|
||||
keepOrder,
|
||||
disabled,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -138,11 +140,11 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen} modal={true}>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
'flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring',
|
||||
`flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -268,7 +270,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
<ScrollArea>
|
||||
<div className="max-h-64 w-full">
|
||||
<CommandGroup>
|
||||
<CommandList className="max-h-64 w-full">
|
||||
<CommandList className="max-h-fit w-full">
|
||||
{options.map((option) => {
|
||||
const isSelected =
|
||||
Array.isArray(value) &&
|
||||
|
||||
@@ -19,6 +19,10 @@ export interface DialogContext {
|
||||
// Alert dialog
|
||||
showAlert: (params: BaseAlertDialogProps) => void;
|
||||
closeAlert: () => void;
|
||||
|
||||
// Create relationship dialog
|
||||
openCreateRelationshipDialog: () => void;
|
||||
closeCreateRelationshipDialog: () => void;
|
||||
}
|
||||
|
||||
export const dialogContext = createContext<DialogContext>({
|
||||
@@ -30,4 +34,6 @@ export const dialogContext = createContext<DialogContext>({
|
||||
closeExportSQLDialog: emptyFn,
|
||||
closeAlert: emptyFn,
|
||||
showAlert: emptyFn,
|
||||
closeCreateRelationshipDialog: emptyFn,
|
||||
openCreateRelationshipDialog: emptyFn,
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
BaseAlertDialog,
|
||||
BaseAlertDialogProps,
|
||||
} from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
||||
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||
|
||||
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
@@ -18,6 +19,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
const [openExportSQLDialogParams, setOpenExportSQLDialogParams] = useState<{
|
||||
targetDatabaseType: DatabaseType;
|
||||
}>({ targetDatabaseType: DatabaseType.GENERIC });
|
||||
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
|
||||
useState(false);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
|
||||
title: '',
|
||||
@@ -55,6 +58,10 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
|
||||
showAlert: showAlertHandler,
|
||||
closeAlert: closeAlertHandler,
|
||||
openCreateRelationshipDialog: () =>
|
||||
setOpenCreateRelationshipDialog(true),
|
||||
closeCreateRelationshipDialog: () =>
|
||||
setOpenCreateRelationshipDialog(false),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -65,6 +72,9 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
{...openExportSQLDialogParams}
|
||||
/>
|
||||
<BaseAlertDialog dialog={{ open: showAlert }} {...alertParams} />
|
||||
<CreateRelationshipDialog
|
||||
dialog={{ open: openCreateRelationshipDialog }}
|
||||
/>
|
||||
</dialogContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,6 +35,13 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setSelectedSidebarSection('tables');
|
||||
setOpenedTableInSidebar(tableId);
|
||||
};
|
||||
|
||||
const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] =
|
||||
(relationshipId) => {
|
||||
showSidePanel();
|
||||
setSelectedSidebarSection('relationships');
|
||||
setOpenedRelationshipInSidebar(relationshipId);
|
||||
};
|
||||
return (
|
||||
<layoutContext.Provider
|
||||
value={{
|
||||
@@ -43,7 +50,7 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
||||
openTableFromSidebar,
|
||||
selectSidebarSection: setSelectedSidebarSection,
|
||||
openedRelationshipInSidebar,
|
||||
openRelationshipFromSidebar: setOpenedRelationshipInSidebar,
|
||||
openRelationshipFromSidebar,
|
||||
closeAllTablesInSidebar,
|
||||
closeAllRelationshipsInSidebar,
|
||||
isSidePanelShowed,
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/dialog/dialog';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import { DialogProps } from '@radix-ui/react-dialog';
|
||||
import { FileOutput, FileMinus2, FileType2 } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { SelectBox, SelectBoxOption } from '@/components/select-box/select-box';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
|
||||
const ErrorMessageRelationshipFieldsNotSameType =
|
||||
'Relationships can only be created between fields of the same type';
|
||||
|
||||
export interface CreateRelationshipDialogProps {
|
||||
dialog: DialogProps;
|
||||
}
|
||||
|
||||
export const CreateRelationshipDialog: React.FC<
|
||||
CreateRelationshipDialogProps
|
||||
> = ({ dialog }) => {
|
||||
const { closeCreateRelationshipDialog } = useDialog();
|
||||
const [primaryTableId, setPrimaryTableId] = useState<string | undefined>();
|
||||
const [primaryFieldId, setPrimaryFieldId] = useState<string | undefined>();
|
||||
const [referencedTableId, setReferencedTableId] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [referencedFieldId, setReferencedFieldId] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const { t } = useTranslation();
|
||||
const { tables, getTable, createRelationship, getField } = useChartDB();
|
||||
const { openRelationshipFromSidebar } = useLayout();
|
||||
const [canCreateRelationship, setCanCreateRelationship] = useState(false);
|
||||
const { fitView, setEdges } = useReactFlow();
|
||||
|
||||
const tableOptions = useMemo(() => {
|
||||
return tables.map(
|
||||
(table) =>
|
||||
({
|
||||
label: table.name,
|
||||
value: table.id,
|
||||
}) as SelectBoxOption
|
||||
);
|
||||
}, [tables]);
|
||||
|
||||
const primaryFieldOptions = useMemo(() => {
|
||||
if (!primaryTableId) return [];
|
||||
const table = getTable(primaryTableId);
|
||||
if (!table) return [];
|
||||
return table.fields.map(
|
||||
(field) =>
|
||||
({
|
||||
label: field.name,
|
||||
value: field.id,
|
||||
description: `(${field.type.name})`,
|
||||
}) as SelectBoxOption
|
||||
);
|
||||
}, [primaryTableId, getTable]);
|
||||
|
||||
const referencedFieldOptions = useMemo(() => {
|
||||
if (!referencedTableId) return [];
|
||||
const table = getTable(referencedTableId);
|
||||
if (!table) return [];
|
||||
return table.fields.map(
|
||||
(field) =>
|
||||
({
|
||||
label: field.name,
|
||||
value: field.id,
|
||||
description: `(${field.type.name})`,
|
||||
}) as SelectBoxOption
|
||||
);
|
||||
}, [referencedTableId, getTable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialog.open) return;
|
||||
setPrimaryTableId(undefined);
|
||||
setPrimaryFieldId(undefined);
|
||||
setReferencedTableId(undefined);
|
||||
setReferencedFieldId(undefined);
|
||||
setErrorMessage('');
|
||||
}, [dialog.open]);
|
||||
|
||||
useEffect(() => {
|
||||
setCanCreateRelationship(false);
|
||||
setErrorMessage('');
|
||||
if (
|
||||
!primaryTableId ||
|
||||
!primaryFieldId ||
|
||||
!referencedTableId ||
|
||||
!referencedFieldId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryField = getField(primaryTableId, primaryFieldId);
|
||||
const referencedField = getField(referencedTableId, referencedFieldId);
|
||||
|
||||
if (!primaryField || !referencedField) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (primaryField.type.id !== referencedField.type.id) {
|
||||
setErrorMessage(ErrorMessageRelationshipFieldsNotSameType);
|
||||
return;
|
||||
}
|
||||
|
||||
setCanCreateRelationship(true);
|
||||
}, [
|
||||
primaryTableId,
|
||||
primaryFieldId,
|
||||
referencedTableId,
|
||||
referencedFieldId,
|
||||
setErrorMessage,
|
||||
getField,
|
||||
]);
|
||||
|
||||
const handleCreateRelationship = useCallback(async () => {
|
||||
if (
|
||||
!primaryTableId ||
|
||||
!primaryFieldId ||
|
||||
!referencedTableId ||
|
||||
!referencedFieldId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relationship = await createRelationship({
|
||||
sourceFieldId: primaryFieldId,
|
||||
sourceTableId: primaryTableId,
|
||||
targetFieldId: referencedFieldId,
|
||||
targetTableId: referencedTableId,
|
||||
});
|
||||
|
||||
setEdges((edges) =>
|
||||
edges.map((edge) =>
|
||||
edge.id == relationship.id
|
||||
? {
|
||||
...edge,
|
||||
selected: true,
|
||||
}
|
||||
: {
|
||||
...edge,
|
||||
selected: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
fitView({
|
||||
duration: 500,
|
||||
maxZoom: 1,
|
||||
minZoom: 1,
|
||||
nodes: [
|
||||
{
|
||||
id: relationship.sourceTableId,
|
||||
},
|
||||
{
|
||||
id: relationship.targetTableId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
openRelationshipFromSidebar(relationship.id);
|
||||
}, [
|
||||
primaryTableId,
|
||||
primaryFieldId,
|
||||
referencedTableId,
|
||||
referencedFieldId,
|
||||
createRelationship,
|
||||
openRelationshipFromSidebar,
|
||||
setEdges,
|
||||
fitView,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialog}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
closeCreateRelationshipDialog();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className="flex flex-col overflow-y-auto" showClose>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{t('create_relationship_dialog.title')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 pt-3">
|
||||
<div className="flex flex-row justify-between gap-2">
|
||||
<div className="flex flex-1 basis-1/2 flex-col gap-2 overflow-hidden">
|
||||
<div className="flex gap-1 text-xs">
|
||||
<FileOutput className="size-4 text-subtitle" />
|
||||
<div className="font-bold text-subtitle">
|
||||
{t(
|
||||
'create_relationship_dialog.primary_table'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 grow-0">
|
||||
<SelectBox
|
||||
className="flex h-8 min-h-8 w-full"
|
||||
options={tableOptions}
|
||||
placeholder={t(
|
||||
'create_relationship_dialog.primary_table_placeholder'
|
||||
)}
|
||||
value={primaryTableId}
|
||||
onChange={(value) => {
|
||||
setPrimaryTableId(value as string);
|
||||
setPrimaryFieldId(undefined);
|
||||
}}
|
||||
emptyPlaceholder={t(
|
||||
'create_relationship_dialog.no_tables_found'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 basis-1/2 flex-col gap-2 overflow-hidden">
|
||||
<div className="flex gap-1 text-xs">
|
||||
<FileType2 className="size-4 text-subtitle" />
|
||||
<div className="font-bold text-subtitle">
|
||||
{t(
|
||||
'create_relationship_dialog.primary_field'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex min-w-0 grow-0">
|
||||
<SelectBox
|
||||
disabled={
|
||||
primaryFieldOptions.length === 0
|
||||
}
|
||||
className="flex h-8 min-h-8 w-full min-w-0"
|
||||
options={primaryFieldOptions}
|
||||
placeholder={t(
|
||||
'create_relationship_dialog.primary_field_placeholder'
|
||||
)}
|
||||
value={primaryFieldId}
|
||||
onChange={(value) =>
|
||||
setPrimaryFieldId(value as string)
|
||||
}
|
||||
emptyPlaceholder={t(
|
||||
'create_relationship_dialog.no_fields_found'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-between gap-2">
|
||||
<div className="flex flex-1 basis-1/2 flex-col gap-2 overflow-hidden">
|
||||
<div className="flex gap-1 text-xs">
|
||||
<FileMinus2 className="size-4 text-subtitle" />
|
||||
<div className="font-bold text-subtitle">
|
||||
{t(
|
||||
'create_relationship_dialog.referenced_table'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 grow-0">
|
||||
<SelectBox
|
||||
className="flex h-8 min-h-8 w-full"
|
||||
options={tableOptions}
|
||||
placeholder={t(
|
||||
'create_relationship_dialog.referenced_table_placeholder'
|
||||
)}
|
||||
value={referencedTableId}
|
||||
onChange={(value) => {
|
||||
setReferencedTableId(value as string);
|
||||
setReferencedFieldId(undefined);
|
||||
}}
|
||||
emptyPlaceholder={t(
|
||||
'create_relationship_dialog.no_tables_found'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 basis-1/2 flex-col gap-2 overflow-hidden">
|
||||
<div className="flex gap-1 text-xs">
|
||||
<FileType2 className="size-4 text-subtitle" />
|
||||
<div className="font-bold text-subtitle">
|
||||
{t(
|
||||
'create_relationship_dialog.referenced_field'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex min-w-0 grow-0">
|
||||
<SelectBox
|
||||
disabled={
|
||||
referencedFieldOptions.length === 0
|
||||
}
|
||||
className="flex h-8 min-h-8 w-full min-w-0"
|
||||
options={referencedFieldOptions}
|
||||
placeholder={t(
|
||||
'create_relationship_dialog.referenced_field_placeholder'
|
||||
)}
|
||||
value={referencedFieldId}
|
||||
onChange={(value) =>
|
||||
setReferencedFieldId(
|
||||
value as string
|
||||
)
|
||||
}
|
||||
emptyPlaceholder={t(
|
||||
'create_relationship_dialog.no_fields_found'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-red-700">{errorMessage}</p>
|
||||
</div>
|
||||
<DialogFooter className="flex !justify-between gap-2">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="secondary">
|
||||
{t('create_relationship_dialog.cancel')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
disabled={!canCreateRelationship}
|
||||
type="button"
|
||||
onClick={handleCreateRelationship}
|
||||
>
|
||||
{t('create_relationship_dialog.create')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -135,6 +135,7 @@ export const en = {
|
||||
relationships_section: {
|
||||
relationships: 'Relationships',
|
||||
filter: 'Filter',
|
||||
add_relationship: 'Add Relationship',
|
||||
collapse: 'Collapse All',
|
||||
relationship: {
|
||||
primary: 'Primary Table',
|
||||
@@ -223,6 +224,22 @@ export const en = {
|
||||
},
|
||||
},
|
||||
|
||||
create_relationship_dialog: {
|
||||
title: 'Create Relationship',
|
||||
primary_table: 'Primary Table',
|
||||
primary_field: 'Primary Field',
|
||||
referenced_table: 'Referenced Table',
|
||||
referenced_field: 'Referenced Field',
|
||||
primary_table_placeholder: 'Select table',
|
||||
primary_field_placeholder: 'Select field',
|
||||
referenced_table_placeholder: 'Select table',
|
||||
referenced_field_placeholder: 'Select field',
|
||||
no_tables_found: 'No tables found',
|
||||
no_fields_found: 'No fields found',
|
||||
create: 'Create',
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
|
||||
relationship_type: {
|
||||
one_to_one: 'One to One',
|
||||
one_to_many: 'One to Many',
|
||||
@@ -232,6 +249,7 @@ export const en = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'New Table',
|
||||
new_relationship: 'New Relationship',
|
||||
},
|
||||
|
||||
table_node_context_menu: {
|
||||
|
||||
@@ -134,6 +134,7 @@ export const es: LanguageTranslation = {
|
||||
},
|
||||
relationships_section: {
|
||||
relationships: 'Relaciones',
|
||||
add_relationship: 'Agregar Relación',
|
||||
filter: 'Filtrar',
|
||||
collapse: 'Colapsar Todo',
|
||||
relationship: {
|
||||
@@ -224,6 +225,22 @@ export const es: LanguageTranslation = {
|
||||
},
|
||||
},
|
||||
|
||||
create_relationship_dialog: {
|
||||
cancel: 'Cancelar',
|
||||
create: 'Crear',
|
||||
no_fields_found: 'No se encontraron campos',
|
||||
no_tables_found: 'No se encontraron tablas',
|
||||
primary_field: 'Campo Primario',
|
||||
primary_table: 'Tabla Primaria',
|
||||
primary_table_placeholder: 'Seleccionar tabla',
|
||||
primary_field_placeholder: 'Seleccionar campo',
|
||||
referenced_field: 'Campo Referenciado',
|
||||
referenced_field_placeholder: 'Seleccionar campo',
|
||||
referenced_table: 'Tabla Referenciada',
|
||||
referenced_table_placeholder: 'Seleccionar tabla',
|
||||
title: 'Crear Relación',
|
||||
},
|
||||
|
||||
relationship_type: {
|
||||
one_to_one: 'Uno a Uno',
|
||||
one_to_many: 'Uno a Muchos',
|
||||
@@ -233,6 +250,7 @@ export const es: LanguageTranslation = {
|
||||
|
||||
canvas_context_menu: {
|
||||
new_table: 'Nueva Tabla',
|
||||
new_relationship: 'Nueva Relación',
|
||||
},
|
||||
|
||||
table_node_context_menu: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@/components/context-menu/context-menu';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -14,6 +15,7 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { createTable } = useChartDB();
|
||||
const { openCreateRelationshipDialog } = useDialog();
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -34,6 +36,10 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||
[createTable, screenToFlowPosition]
|
||||
);
|
||||
|
||||
const createRelationshipHandler = useCallback(() => {
|
||||
openCreateRelationshipDialog();
|
||||
}, [openCreateRelationshipDialog]);
|
||||
|
||||
if (!isDesktop) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -45,6 +51,9 @@ export const CanvasContextMenu: React.FC<React.PropsWithChildren> = ({
|
||||
<ContextMenuItem onClick={createTableHandler}>
|
||||
{t('canvas_context_menu.new_table')}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={createRelationshipHandler}>
|
||||
{t('canvas_context_menu.new_relationship')}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
@@ -232,20 +232,20 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const relationship = await createRelationship({
|
||||
createRelationship({
|
||||
sourceTableId,
|
||||
targetTableId,
|
||||
sourceFieldId,
|
||||
targetFieldId,
|
||||
});
|
||||
return setEdges((edges) =>
|
||||
addEdge<TableEdgeType>(
|
||||
{ ...params, data: { relationship }, id: relationship.id },
|
||||
edges
|
||||
)
|
||||
);
|
||||
// return setEdges((edges) =>
|
||||
// addEdge<TableEdgeType>(
|
||||
// { ...params, data: { relationship }, id: relationship.id },
|
||||
// edges
|
||||
// )
|
||||
// );
|
||||
},
|
||||
[setEdges, createRelationship, getField, toast]
|
||||
[createRelationship, getField, toast]
|
||||
);
|
||||
|
||||
const onEdgesChangeHandler: OnEdgesChange<TableEdgeType> = useCallback(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Handle,
|
||||
Position,
|
||||
@@ -28,16 +28,30 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
|
||||
const { removeField, relationships } = useChartDB();
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const connection = useConnection();
|
||||
const isTarget =
|
||||
connection.inProgress && connection.fromNode.id !== tableNodeId;
|
||||
const numberOfEdgesToField = relationships.filter(
|
||||
(relationship) =>
|
||||
relationship.targetTableId === tableNodeId &&
|
||||
relationship.targetFieldId === field.id
|
||||
).length;
|
||||
const isTarget = useMemo(
|
||||
() =>
|
||||
connection.inProgress && connection.fromNode.id !== tableNodeId,
|
||||
[connection, tableNodeId]
|
||||
);
|
||||
const numberOfEdgesToField = useMemo(
|
||||
() =>
|
||||
relationships.filter(
|
||||
(relationship) =>
|
||||
relationship.targetTableId === tableNodeId &&
|
||||
relationship.targetFieldId === field.id
|
||||
).length,
|
||||
[relationships, tableNodeId, field.id]
|
||||
);
|
||||
|
||||
const previousNumberOfEdgesToFieldRef = useRef(numberOfEdgesToField);
|
||||
|
||||
useEffect(() => {
|
||||
updateNodeInternals(tableNodeId);
|
||||
if (
|
||||
previousNumberOfEdgesToFieldRef.current !== numberOfEdgesToField
|
||||
) {
|
||||
updateNodeInternals(tableNodeId);
|
||||
previousNumberOfEdgesToFieldRef.current = numberOfEdgesToField;
|
||||
}
|
||||
}, [tableNodeId, updateNodeInternals, numberOfEdgesToField]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,7 +20,12 @@ import {
|
||||
RelationshipType,
|
||||
} from '@/lib/domain/db-relationship';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
import { FileMinus2, FileOutput, Trash2 } from 'lucide-react';
|
||||
import {
|
||||
FileMinus2,
|
||||
FileOutput,
|
||||
Trash2,
|
||||
ChevronsLeftRightEllipsis,
|
||||
} from 'lucide-react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -79,9 +84,9 @@ export const RelationshipListItemContent: React.FC<
|
||||
<div className="my-1 flex flex-col rounded-b-md px-1">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex items-center justify-between gap-1 text-xs">
|
||||
<div className="flex flex-col gap-2 overflow-hidden text-xs">
|
||||
<div className="flex basis-1/2 flex-col gap-2 overflow-hidden text-xs">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<FileMinus2 className="size-4 text-subtitle" />
|
||||
<FileOutput className="size-4 text-subtitle" />
|
||||
<div className="font-bold text-subtitle">
|
||||
{t(
|
||||
'side_panel.relationships_section.relationship.primary'
|
||||
@@ -90,7 +95,7 @@ export const RelationshipListItemContent: React.FC<
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="truncate text-sm ">
|
||||
<div className="truncate text-left text-sm">
|
||||
{sourceTable?.name}({sourceField?.name})
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
@@ -99,9 +104,9 @@ export const RelationshipListItemContent: React.FC<
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 overflow-hidden text-xs">
|
||||
<div className="flex basis-1/2 flex-col gap-2 overflow-hidden text-xs">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<FileOutput className="size-4 text-subtitle" />
|
||||
<FileMinus2 className="size-4 text-subtitle" />
|
||||
<div className="font-bold text-subtitle">
|
||||
{t(
|
||||
'side_panel.relationships_section.relationship.foreign'
|
||||
@@ -110,7 +115,7 @@ export const RelationshipListItemContent: React.FC<
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="truncate text-sm ">
|
||||
<div className="truncate text-left text-sm ">
|
||||
{targetTable?.name}({targetField?.name})
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
@@ -122,7 +127,7 @@ export const RelationshipListItemContent: React.FC<
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 text-xs">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<FileOutput className="size-4 text-subtitle" />
|
||||
<ChevronsLeftRightEllipsis className="size-4 text-subtitle" />
|
||||
<div className="font-bold text-subtitle">
|
||||
{t(
|
||||
'side_panel.relationships_section.relationship.cardinality'
|
||||
|
||||
@@ -45,14 +45,20 @@ export const RelationshipListItemHeader: React.FC<
|
||||
|
||||
const editRelationshipName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (relationshipName.trim()) {
|
||||
if (relationshipName.trim() && relationshipName !== relationship.name) {
|
||||
updateRelationship(relationship.id, {
|
||||
name: relationshipName.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
setEditMode(false);
|
||||
}, [relationshipName, relationship.id, updateRelationship, editMode]);
|
||||
}, [
|
||||
relationshipName,
|
||||
relationship.id,
|
||||
updateRelationship,
|
||||
editMode,
|
||||
relationship.name,
|
||||
]);
|
||||
|
||||
useClickAway(inputRef, editRelationshipName);
|
||||
useKeyPressEvent('Enter', editRelationshipName);
|
||||
@@ -73,12 +79,10 @@ export const RelationshipListItemHeader: React.FC<
|
||||
? {
|
||||
...edge,
|
||||
selected: true,
|
||||
animated: true,
|
||||
}
|
||||
: {
|
||||
...edge,
|
||||
selected: false,
|
||||
animated: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { ListCollapse } from 'lucide-react';
|
||||
import { Input } from '@/components/input/input';
|
||||
@@ -12,11 +12,13 @@ import { useLayout } from '@/hooks/use-layout';
|
||||
import { EmptyState } from '@/components/empty-state/empty-state';
|
||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Workflow } from 'lucide-react';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
|
||||
export interface RelationshipsSectionProps {}
|
||||
|
||||
@@ -25,6 +27,7 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
||||
const [filterText, setFilterText] = React.useState('');
|
||||
const { closeAllRelationshipsInSidebar } = useLayout();
|
||||
const { t } = useTranslation();
|
||||
const { openCreateRelationshipDialog } = useDialog();
|
||||
|
||||
const filteredRelationships = useMemo(() => {
|
||||
const filterName: (relationship: DBRelationship) => boolean = (
|
||||
@@ -41,6 +44,11 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
||||
return relationships.filter(filterSchema).filter(filterName);
|
||||
}, [relationships, filterText, filteredSchemas]);
|
||||
|
||||
const handleCreateRelationship = useCallback(async () => {
|
||||
setFilterText('');
|
||||
openCreateRelationshipDialog();
|
||||
}, [openCreateRelationshipDialog, setFilterText]);
|
||||
|
||||
return (
|
||||
<section className="flex flex-1 flex-col overflow-hidden px-2">
|
||||
<div className="flex items-center justify-between gap-4 py-1">
|
||||
@@ -73,6 +81,14 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-8 p-2 text-xs"
|
||||
onClick={handleCreateRelationship}
|
||||
>
|
||||
<Workflow className="h-4" />
|
||||
{t('side_panel.relationships_section.add_relationship')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<ScrollArea className="h-full">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { TableList } from './table-list/table-list';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { Table, ListCollapse } from 'lucide-react';
|
||||
@@ -37,7 +37,7 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
|
||||
return tables.filter(filterSchema).filter(filterTableName);
|
||||
}, [tables, filterText, filteredSchemas]);
|
||||
|
||||
const handleCreateTable = async () => {
|
||||
const handleCreateTable = useCallback(async () => {
|
||||
setFilterText('');
|
||||
const padding = 80;
|
||||
const centerX = -viewport.x / viewport.zoom + padding / viewport.zoom;
|
||||
@@ -48,7 +48,14 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
|
||||
y: centerY,
|
||||
});
|
||||
openTableFromSidebar(table.id);
|
||||
};
|
||||
}, [
|
||||
createTable,
|
||||
openTableFromSidebar,
|
||||
setFilterText,
|
||||
viewport.x,
|
||||
viewport.y,
|
||||
viewport.zoom,
|
||||
]);
|
||||
|
||||
return (
|
||||
<section
|
||||
|
||||
@@ -30,15 +30,15 @@ const routes: RouteObject[] = [
|
||||
<ChartDBProvider>
|
||||
<HistoryProvider>
|
||||
<ThemeProvider>
|
||||
<DialogProvider>
|
||||
<ReactFlowProvider>
|
||||
<ReactFlowProvider>
|
||||
<DialogProvider>
|
||||
<ExportImageProvider>
|
||||
<KeyboardShortcutsProvider>
|
||||
<EditorPage />
|
||||
</KeyboardShortcutsProvider>
|
||||
</ExportImageProvider>
|
||||
</ReactFlowProvider>
|
||||
</DialogProvider>
|
||||
</DialogProvider>
|
||||
</ReactFlowProvider>
|
||||
</ThemeProvider>
|
||||
</HistoryProvider>
|
||||
</ChartDBProvider>
|
||||
|
||||
Reference in New Issue
Block a user