diff --git a/package-lock.json b/package-lock.json index 695a1880..53a07603 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 2f055839..ec3b2354 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/select-box/select-box.tsx b/src/components/select-box/select-box.tsx index 88f7c720..f88d9766 100644 --- a/src/components/select-box/select-box.tsx +++ b/src/components/select-box/select-box.tsx @@ -40,6 +40,7 @@ interface SelectBoxProps { clearText?: string; showClear?: boolean; keepOrder?: boolean; + disabled?: boolean; } export const SelectBox = React.forwardRef( @@ -59,6 +60,7 @@ export const SelectBox = React.forwardRef( clearText, showClear, keepOrder, + disabled, }, ref ) => { @@ -138,11 +140,11 @@ export const SelectBox = React.forwardRef( ); return ( - +
@@ -268,7 +270,7 @@ export const SelectBox = React.forwardRef(
- + {options.map((option) => { const isSelected = Array.isArray(value) && diff --git a/src/context/dialog-context/dialog-context.tsx b/src/context/dialog-context/dialog-context.tsx index a7eb04f8..c845727c 100644 --- a/src/context/dialog-context/dialog-context.tsx +++ b/src/context/dialog-context/dialog-context.tsx @@ -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({ @@ -30,4 +34,6 @@ export const dialogContext = createContext({ closeExportSQLDialog: emptyFn, closeAlert: emptyFn, showAlert: emptyFn, + closeCreateRelationshipDialog: emptyFn, + openCreateRelationshipDialog: emptyFn, }); diff --git a/src/context/dialog-context/dialog-provider.tsx b/src/context/dialog-context/dialog-provider.tsx index 452e6510..de77cd5e 100644 --- a/src/context/dialog-context/dialog-provider.tsx +++ b/src/context/dialog-context/dialog-provider.tsx @@ -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 = ({ children, @@ -18,6 +19,8 @@ export const DialogProvider: React.FC = ({ const [openExportSQLDialogParams, setOpenExportSQLDialogParams] = useState<{ targetDatabaseType: DatabaseType; }>({ targetDatabaseType: DatabaseType.GENERIC }); + const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] = + useState(false); const [showAlert, setShowAlert] = useState(false); const [alertParams, setAlertParams] = useState({ title: '', @@ -55,6 +58,10 @@ export const DialogProvider: React.FC = ({ closeExportSQLDialog: () => setOpenExportSQLDialog(false), showAlert: showAlertHandler, closeAlert: closeAlertHandler, + openCreateRelationshipDialog: () => + setOpenCreateRelationshipDialog(true), + closeCreateRelationshipDialog: () => + setOpenCreateRelationshipDialog(false), }} > {children} @@ -65,6 +72,9 @@ export const DialogProvider: React.FC = ({ {...openExportSQLDialogParams} /> + ); }; diff --git a/src/context/layout-context/layout-provider.tsx b/src/context/layout-context/layout-provider.tsx index bd5a254e..37667ae5 100644 --- a/src/context/layout-context/layout-provider.tsx +++ b/src/context/layout-context/layout-provider.tsx @@ -35,6 +35,13 @@ export const LayoutProvider: React.FC = ({ setSelectedSidebarSection('tables'); setOpenedTableInSidebar(tableId); }; + + const openRelationshipFromSidebar: LayoutContext['openRelationshipFromSidebar'] = + (relationshipId) => { + showSidePanel(); + setSelectedSidebarSection('relationships'); + setOpenedRelationshipInSidebar(relationshipId); + }; return ( = ({ openTableFromSidebar, selectSidebarSection: setSelectedSidebarSection, openedRelationshipInSidebar, - openRelationshipFromSidebar: setOpenedRelationshipInSidebar, + openRelationshipFromSidebar, closeAllTablesInSidebar, closeAllRelationshipsInSidebar, isSidePanelShowed, diff --git a/src/dialogs/create-relationship-dialog/create-relationship-dialog.tsx b/src/dialogs/create-relationship-dialog/create-relationship-dialog.tsx new file mode 100644 index 00000000..766fc7e4 --- /dev/null +++ b/src/dialogs/create-relationship-dialog/create-relationship-dialog.tsx @@ -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(); + const [primaryFieldId, setPrimaryFieldId] = useState(); + 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 ( + { + if (!open) { + closeCreateRelationshipDialog(); + } + }} + > + + + + {t('create_relationship_dialog.title')} + + +
+
+
+
+ +
+ {t( + 'create_relationship_dialog.primary_table' + )} +
+
+
+ { + setPrimaryTableId(value as string); + setPrimaryFieldId(undefined); + }} + emptyPlaceholder={t( + 'create_relationship_dialog.no_tables_found' + )} + /> +
+
+
+
+ +
+ {t( + 'create_relationship_dialog.primary_field' + )} +
+
+
+
+ + setPrimaryFieldId(value as string) + } + emptyPlaceholder={t( + 'create_relationship_dialog.no_fields_found' + )} + /> +
+
+
+
+ +
+
+
+ +
+ {t( + 'create_relationship_dialog.referenced_table' + )} +
+
+
+ { + setReferencedTableId(value as string); + setReferencedFieldId(undefined); + }} + emptyPlaceholder={t( + 'create_relationship_dialog.no_tables_found' + )} + /> +
+
+
+
+ +
+ {t( + 'create_relationship_dialog.referenced_field' + )} +
+
+
+
+ + setReferencedFieldId( + value as string + ) + } + emptyPlaceholder={t( + 'create_relationship_dialog.no_fields_found' + )} + /> +
+
+
+
+

{errorMessage}

+
+ + + + + + + + +
+
+ ); +}; diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 860249ab..d138642a 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -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: { diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index a4dcf3e4..bb210515 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -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: { diff --git a/src/pages/editor-page/canvas/canvas-context-menu.tsx b/src/pages/editor-page/canvas/canvas-context-menu.tsx index 49183c0c..e0a8488f 100644 --- a/src/pages/editor-page/canvas/canvas-context-menu.tsx +++ b/src/pages/editor-page/canvas/canvas-context-menu.tsx @@ -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 = ({ children, }) => { const { createTable } = useChartDB(); + const { openCreateRelationshipDialog } = useDialog(); const { screenToFlowPosition } = useReactFlow(); const { t } = useTranslation(); @@ -34,6 +36,10 @@ export const CanvasContextMenu: React.FC = ({ [createTable, screenToFlowPosition] ); + const createRelationshipHandler = useCallback(() => { + openCreateRelationshipDialog(); + }, [openCreateRelationshipDialog]); + if (!isDesktop) { return <>{children}; } @@ -45,6 +51,9 @@ export const CanvasContextMenu: React.FC = ({ {t('canvas_context_menu.new_table')} + + {t('canvas_context_menu.new_relationship')} + ); diff --git a/src/pages/editor-page/canvas/canvas.tsx b/src/pages/editor-page/canvas/canvas.tsx index d98a85fd..d5cbac5c 100644 --- a/src/pages/editor-page/canvas/canvas.tsx +++ b/src/pages/editor-page/canvas/canvas.tsx @@ -232,20 +232,20 @@ export const Canvas: React.FC = ({ initialTables }) => { return; } - const relationship = await createRelationship({ + createRelationship({ sourceTableId, targetTableId, sourceFieldId, targetFieldId, }); - return setEdges((edges) => - addEdge( - { ...params, data: { relationship }, id: relationship.id }, - edges - ) - ); + // return setEdges((edges) => + // addEdge( + // { ...params, data: { relationship }, id: relationship.id }, + // edges + // ) + // ); }, - [setEdges, createRelationship, getField, toast] + [createRelationship, getField, toast] ); const onEdgesChangeHandler: OnEdgesChange = useCallback( diff --git a/src/pages/editor-page/canvas/table-node/table-node-field.tsx b/src/pages/editor-page/canvas/table-node/table-node-field.tsx index 44183fc4..10a2c9e7 100644 --- a/src/pages/editor-page/canvas/table-node/table-node-field.tsx +++ b/src/pages/editor-page/canvas/table-node/table-node-field.tsx @@ -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 = 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 ( diff --git a/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-content/relationship-list-item-content.tsx b/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-content/relationship-list-item-content.tsx index 94b0c8b9..81936b9a 100644 --- a/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-content/relationship-list-item-content.tsx +++ b/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-content/relationship-list-item-content.tsx @@ -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<
-
+
- +
{t( 'side_panel.relationships_section.relationship.primary' @@ -90,7 +95,7 @@ export const RelationshipListItemContent: React.FC<
-
+
{sourceTable?.name}({sourceField?.name})
@@ -99,9 +104,9 @@ export const RelationshipListItemContent: React.FC<
-
+
- +
{t( 'side_panel.relationships_section.relationship.foreign' @@ -110,7 +115,7 @@ export const RelationshipListItemContent: React.FC<
-
+
{targetTable?.name}({targetField?.name})
@@ -122,7 +127,7 @@ export const RelationshipListItemContent: React.FC<
- +
{t( 'side_panel.relationships_section.relationship.cardinality' diff --git a/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-header/relationship-list-item-header.tsx b/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-header/relationship-list-item-header.tsx index 24d58549..27e99749 100644 --- a/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-header/relationship-list-item-header.tsx +++ b/src/pages/editor-page/side-panel/relationships-section/relationship-list/relationship-list-item/relationship-list-item-header/relationship-list-item-header.tsx @@ -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, } ) ); diff --git a/src/pages/editor-page/side-panel/relationships-section/relationships-section.tsx b/src/pages/editor-page/side-panel/relationships-section/relationships-section.tsx index e7ed2c7f..7d89a975 100644 --- a/src/pages/editor-page/side-panel/relationships-section/relationships-section.tsx +++ b/src/pages/editor-page/side-panel/relationships-section/relationships-section.tsx @@ -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 = () => { 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 = () => { return relationships.filter(filterSchema).filter(filterName); }, [relationships, filterText, filteredSchemas]); + const handleCreateRelationship = useCallback(async () => { + setFilterText(''); + openCreateRelationshipDialog(); + }, [openCreateRelationshipDialog, setFilterText]); + return (
@@ -73,6 +81,14 @@ export const RelationshipsSection: React.FC = () => { onChange={(e) => setFilterText(e.target.value)} />
+
diff --git a/src/pages/editor-page/side-panel/tables-section/tables-section.tsx b/src/pages/editor-page/side-panel/tables-section/tables-section.tsx index 7844d194..76ae2e99 100644 --- a/src/pages/editor-page/side-panel/tables-section/tables-section.tsx +++ b/src/pages/editor-page/side-panel/tables-section/tables-section.tsx @@ -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 = () => { 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 = () => { y: centerY, }); openTableFromSidebar(table.id); - }; + }, [ + createTable, + openTableFromSidebar, + setFilterText, + viewport.x, + viewport.y, + viewport.zoom, + ]); return (
- - + + - - + +