add relationship from side panel

This commit is contained in:
Guy Ben-Aharon
2024-09-15 18:33:32 +03:00
committed by Guy Ben-Aharon
parent fa346ae607
commit ffbbc3af40
17 changed files with 505 additions and 630 deletions

592
package-lock.json generated
View File

@@ -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"

View File

@@ -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",

View File

@@ -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) &&

View File

@@ -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,
});

View File

@@ -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>
);
};

View File

@@ -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,

View File

@@ -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>
);
};

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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>
);

View File

@@ -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(

View File

@@ -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 (

View File

@@ -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'

View File

@@ -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,
}
)
);

View File

@@ -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">

View File

@@ -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

View File

@@ -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>