mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-02 13:03:17 +00:00
Compare commits
13 Commits
jf/fix_add
...
jf/fix_lar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba46643cd0 | ||
|
|
4520f8b1f7 | ||
|
|
712bdf5b95 | ||
|
|
d7c9536272 | ||
|
|
815a52f192 | ||
|
|
f1a4298362 | ||
|
|
b8f2141bd2 | ||
|
|
eaebe34768 | ||
|
|
0d623a86b1 | ||
|
|
19fd94c6bd | ||
|
|
0da3caeeac | ||
|
|
cb2ba66233 | ||
|
|
8a2267281b |
13
index.html
13
index.html
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="max-image-preview:large" />
|
||||
<meta name="robots" content="noindex, max-image-preview:large" />
|
||||
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
|
||||
<link rel="canonical" href="https://chartdb.io" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
@@ -16,14 +16,19 @@
|
||||
<script src="/config.js"></script>
|
||||
<script>
|
||||
// Load analytics only if not disabled
|
||||
(function() {
|
||||
const disableAnalytics = (window.env && window.env.DISABLE_ANALYTICS === 'true') ||
|
||||
(typeof process !== 'undefined' && process.env && process.env.VITE_DISABLE_ANALYTICS === 'true');
|
||||
(function () {
|
||||
const disableAnalytics =
|
||||
(window.env && window.env.DISABLE_ANALYTICS === 'true') ||
|
||||
(typeof process !== 'undefined' &&
|
||||
process.env &&
|
||||
process.env.VITE_DISABLE_ANALYTICS === 'true');
|
||||
|
||||
if (!disableAnalytics) {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.usefathom.com/script.js';
|
||||
script.setAttribute('data-site', 'PRHIVBNN');
|
||||
script.setAttribute('data-canonical', 'false');
|
||||
script.setAttribute('data-spa', 'auto');
|
||||
script.defer = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
588
package-lock.json
generated
588
package-lock.json
generated
@@ -18,22 +18,22 @@
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-menubar": "^1.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@xyflow/react": "^12.8.2",
|
||||
"ahooks": "^3.8.1",
|
||||
@@ -2121,23 +2121,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz",
|
||||
"integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==",
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz",
|
||||
"integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.5",
|
||||
"@radix-ui/react-focus-guards": "1.1.1",
|
||||
"@radix-ui/react-focus-scope": "1.1.2",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-portal": "1.1.4",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-slot": "1.1.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.10",
|
||||
"@radix-ui/react-focus-guards": "1.1.2",
|
||||
"@radix-ui/react-focus-scope": "1.1.7",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.4",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"aria-hidden": "^1.2.4",
|
||||
"react-remove-scroll": "^2.6.3"
|
||||
},
|
||||
@@ -2156,17 +2156,53 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
|
||||
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
||||
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz",
|
||||
"integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==",
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
|
||||
"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -2183,15 +2219,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
|
||||
"integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz",
|
||||
"integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==",
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
|
||||
"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -2208,14 +2259,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz",
|
||||
"integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==",
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
|
||||
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
|
||||
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
|
||||
"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -2233,12 +2326,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
|
||||
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.2"
|
||||
"@radix-ui/react-slot": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -2255,13 +2348,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
|
||||
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -2273,6 +2382,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
|
||||
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
||||
@@ -2941,12 +3083,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-separator": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz",
|
||||
"integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==",
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
|
||||
"integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.2"
|
||||
"@radix-ui/react-primitive": "2.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -2964,12 +3106,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
|
||||
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.2"
|
||||
"@radix-ui/react-slot": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -2986,24 +3128,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||
@@ -3156,23 +3280,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz",
|
||||
"integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==",
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz",
|
||||
"integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.5",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-popper": "1.2.2",
|
||||
"@radix-ui/react-portal": "1.1.4",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-slot": "1.1.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||
"@radix-ui/react-visually-hidden": "1.1.2"
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.10",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-popper": "1.2.7",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.4",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3189,13 +3313,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": {
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz",
|
||||
"integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
|
||||
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
|
||||
"integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.2"
|
||||
"@radix-ui/react-primitive": "2.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3212,17 +3342,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
||||
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz",
|
||||
"integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==",
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
|
||||
"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3239,22 +3399,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
|
||||
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz",
|
||||
"integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==",
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
|
||||
"integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
"@radix-ui/react-arrow": "1.1.2",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0",
|
||||
"@radix-ui/react-use-rect": "1.1.0",
|
||||
"@radix-ui/react-use-size": "1.1.0",
|
||||
"@radix-ui/rect": "1.1.0"
|
||||
"@radix-ui/react-arrow": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1",
|
||||
"@radix-ui/react-use-rect": "1.1.1",
|
||||
"@radix-ui/react-use-size": "1.1.1",
|
||||
"@radix-ui/rect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3272,13 +3450,37 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz",
|
||||
"integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==",
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
|
||||
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
|
||||
"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3296,12 +3498,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
|
||||
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.2"
|
||||
"@radix-ui/react-slot": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3318,13 +3520,98 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
|
||||
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
|
||||
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-rect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
|
||||
"integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/rect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-size": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
|
||||
"integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3337,12 +3624,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz",
|
||||
"integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
|
||||
"integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.2"
|
||||
"@radix-ui/react-primitive": "2.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -3359,6 +3646,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/rect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
|
||||
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||
@@ -3392,6 +3685,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-effect-event": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
|
||||
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
|
||||
|
||||
@@ -26,22 +26,22 @@
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-menubar": "^1.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@xyflow/react": "^12.8.2",
|
||||
"ahooks": "^3.8.1",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /
|
||||
|
||||
Sitemap: https://app.chartdb.io/sitemap.xml
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Cross2Icon } from '@radix-ui/react-icons';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ScrollArea } from '../scroll-area/scroll-area';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
@@ -32,28 +33,75 @@ const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||
showClose?: boolean;
|
||||
showBack?: boolean;
|
||||
backButtonClassName?: string;
|
||||
blurBackground?: boolean;
|
||||
forceOverlay?: boolean;
|
||||
onBackClick?: () => void;
|
||||
}
|
||||
>(({ className, children, showClose, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showClose && (
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<Cross2Icon className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
showClose,
|
||||
showBack,
|
||||
onBackClick,
|
||||
backButtonClassName,
|
||||
blurBackground,
|
||||
forceOverlay,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<DialogPortal>
|
||||
{forceOverlay ? (
|
||||
<div
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
{
|
||||
'bg-black/80': !blurBackground,
|
||||
'bg-black/30 backdrop-blur-sm': blurBackground,
|
||||
}
|
||||
)}
|
||||
data-state="open"
|
||||
/>
|
||||
) : null}
|
||||
<DialogOverlay
|
||||
className={cn({
|
||||
'bg-black/30 backdrop-blur-sm': blurBackground,
|
||||
})}
|
||||
/>
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showBack && (
|
||||
<button
|
||||
onClick={() => onBackClick?.()}
|
||||
className={cn(
|
||||
'absolute left-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground',
|
||||
backButtonClassName
|
||||
)}
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
{showClose && (
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<Cross2Icon className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
);
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
|
||||
@@ -2,16 +2,13 @@ import React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -11,40 +11,21 @@ const PopoverAnchor = PopoverPrimitive.Anchor;
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
|
||||
container?: HTMLElement | null;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{ className, align = 'center', sideOffset = 4, container, ...props },
|
||||
ref
|
||||
) => {
|
||||
const Content = (
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
// If container is explicitly null, don't use Portal
|
||||
if (container === null) {
|
||||
return Content;
|
||||
}
|
||||
|
||||
// Otherwise, use Portal (default behavior)
|
||||
return (
|
||||
<PopoverPrimitive.Portal container={container}>
|
||||
{Content}
|
||||
</PopoverPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
);
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
||||
|
||||
@@ -229,7 +229,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
<CommandItem
|
||||
className="flex items-center"
|
||||
key={option.value}
|
||||
value={option.label}
|
||||
keywords={option.regex ? [option.regex] : undefined}
|
||||
onSelect={() =>
|
||||
handleSelect(
|
||||
@@ -281,7 +280,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={onOpenChange}>
|
||||
<Popover open={isOpen} onOpenChange={onOpenChange} modal={true}>
|
||||
<PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -355,7 +354,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
popoverClassName
|
||||
)}
|
||||
align="center"
|
||||
container={null}
|
||||
>
|
||||
<Command
|
||||
filter={(value, search, keywords) => {
|
||||
@@ -423,30 +421,25 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
|
||||
<CommandEmpty>
|
||||
{emptyPlaceholder ?? 'No results found.'}
|
||||
</CommandEmpty>
|
||||
|
||||
<ScrollArea>
|
||||
<div className="max-h-64">
|
||||
{hasGroups ? (
|
||||
Object.entries(groups).map(
|
||||
([groupName, groupOptions]) => (
|
||||
<CommandGroup
|
||||
key={groupName}
|
||||
heading={groupName}
|
||||
>
|
||||
<CommandList>
|
||||
{groupOptions.map(
|
||||
renderOption
|
||||
)}
|
||||
</CommandList>
|
||||
</CommandGroup>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<CommandGroup>
|
||||
<CommandList>
|
||||
{options.map(renderOption)}
|
||||
</CommandList>
|
||||
</CommandGroup>
|
||||
)}
|
||||
<div className="max-h-64 w-full">
|
||||
<CommandList className="max-h-fit w-full">
|
||||
{hasGroups
|
||||
? Object.entries(groups).map(
|
||||
([groupName, groupOptions]) => (
|
||||
<CommandGroup
|
||||
key={groupName}
|
||||
heading={groupName}
|
||||
>
|
||||
{groupOptions.map(
|
||||
renderOption
|
||||
)}
|
||||
</CommandGroup>
|
||||
)
|
||||
)
|
||||
: options.map(renderOption)}
|
||||
</CommandList>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
|
||||
@@ -29,6 +29,7 @@ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||
const SIDEBAR_WIDTH = '16rem';
|
||||
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||
const SIDEBAR_WIDTH_ICON = '3rem';
|
||||
const SIDEBAR_WIDTH_ICON_EXTENDED = '4rem';
|
||||
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
||||
|
||||
type SidebarContext = {
|
||||
@@ -142,6 +143,8 @@ const SidebarProvider = React.forwardRef<
|
||||
{
|
||||
'--sidebar-width': SIDEBAR_WIDTH,
|
||||
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
||||
'--sidebar-width-icon-extended':
|
||||
SIDEBAR_WIDTH_ICON_EXTENDED,
|
||||
...style,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
@@ -166,7 +169,7 @@ const Sidebar = React.forwardRef<
|
||||
React.ComponentProps<'div'> & {
|
||||
side?: 'left' | 'right';
|
||||
variant?: 'sidebar' | 'floating' | 'inset';
|
||||
collapsible?: 'offcanvas' | 'icon' | 'none';
|
||||
collapsible?: 'offcanvas' | 'icon' | 'icon-extended' | 'none';
|
||||
}
|
||||
>(
|
||||
(
|
||||
@@ -245,8 +248,8 @@ const Sidebar = React.forwardRef<
|
||||
'group-data-[collapsible=offcanvas]:w-0',
|
||||
'group-data-[side=right]:rotate-180',
|
||||
variant === 'floating' || variant === 'inset'
|
||||
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
|
||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
|
||||
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4))]'
|
||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended]'
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
@@ -257,8 +260,8 @@ const Sidebar = React.forwardRef<
|
||||
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
||||
// Adjust the padding for floating and inset variants.
|
||||
variant === 'floating' || variant === 'inset'
|
||||
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
|
||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
|
||||
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)] group-data-[collapsible=icon-extended]:w-[calc(var(--sidebar-width-icon-extended)_+_theme(spacing.4)_+2px)]'
|
||||
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[collapsible=icon-extended]:w-[--sidebar-width-icon-extended] group-data-[side=left]:border-r group-data-[side=right]:border-l',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -421,7 +424,7 @@ const SidebarContent = React.forwardRef<
|
||||
ref={ref}
|
||||
data-sidebar="content"
|
||||
className={cn(
|
||||
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
|
||||
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden group-data-[collapsible=icon-extended]:overflow-hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -461,6 +464,7 @@ const SidebarGroupLabel = React.forwardRef<
|
||||
className={cn(
|
||||
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
||||
'group-data-[collapsible=icon-extended]:-mt-8 group-data-[collapsible=icon-extended]:opacity-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -483,7 +487,7 @@ const SidebarGroupAction = React.forwardRef<
|
||||
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
// Increases the hit area of the button on mobile.
|
||||
'after:absolute after:-inset-2 after:md:hidden',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -532,7 +536,7 @@ const SidebarMenuItem = React.forwardRef<
|
||||
SidebarMenuItem.displayName = 'SidebarMenuItem';
|
||||
|
||||
const sidebarMenuButtonVariants = cva(
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon-extended]:h-auto group-data-[collapsible=icon-extended]:flex-col group-data-[collapsible=icon-extended]:gap-1 group-data-[collapsible=icon-extended]:p-2 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate group-data-[collapsible=icon-extended]:[&>span]:w-full group-data-[collapsible=icon-extended]:[&>span]:text-center group-data-[collapsible=icon-extended]:[&>span]:text-[10px] group-data-[collapsible=icon-extended]:[&>span]:leading-tight [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -636,7 +640,7 @@ const SidebarMenuAction = React.forwardRef<
|
||||
'peer-data-[size=sm]/menu-button:top-1',
|
||||
'peer-data-[size=default]/menu-button:top-1.5',
|
||||
'peer-data-[size=lg]/menu-button:top-2.5',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||
showOnHover &&
|
||||
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
|
||||
className
|
||||
@@ -753,7 +757,7 @@ const SidebarMenuSubButton = React.forwardRef<
|
||||
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
||||
size === 'sm' && 'text-xs',
|
||||
size === 'md' && 'text-sm',
|
||||
'group-data-[collapsible=icon]:hidden',
|
||||
'group-data-[collapsible=icon]:hidden group-data-[collapsible=icon-extended]:hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -13,15 +13,17 @@ const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
// <TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
// </TooltipPrimitive.Portal>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ interface CanvasProviderProps {
|
||||
}
|
||||
|
||||
export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
const { tables, relationships, updateTablesState, databaseType } =
|
||||
const { tables, relationships, updateTablesState, databaseType, areas } =
|
||||
useChartDB();
|
||||
const { filter } = useDiagramFilter();
|
||||
const { fitView } = useReactFlow();
|
||||
@@ -44,6 +44,7 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
},
|
||||
})
|
||||
),
|
||||
areas,
|
||||
mode: 'all',
|
||||
});
|
||||
|
||||
@@ -86,6 +87,7 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
|
||||
updateTablesState,
|
||||
fitView,
|
||||
databaseType,
|
||||
areas,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import { deepCopy, generateId } from '@/lib/utils';
|
||||
import { randomColor } from '@/lib/colors';
|
||||
import { defaultTableColor, defaultAreaColor } from '@/lib/colors';
|
||||
import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
|
||||
import { chartDBContext } from './chartdb-context';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
@@ -337,7 +337,7 @@ export const ChartDBProvider: React.FC<
|
||||
},
|
||||
],
|
||||
indexes: [],
|
||||
color: randomColor(),
|
||||
color: defaultTableColor,
|
||||
createdAt: Date.now(),
|
||||
isView: false,
|
||||
order: tables.length,
|
||||
@@ -1412,7 +1412,7 @@ export const ChartDBProvider: React.FC<
|
||||
y: 0,
|
||||
width: 300,
|
||||
height: 200,
|
||||
color: randomColor(),
|
||||
color: defaultAreaColor,
|
||||
...attributes,
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createContext } from 'react';
|
||||
|
||||
export interface DiagramFilterContext {
|
||||
filter?: DiagramFilter;
|
||||
loading: boolean;
|
||||
|
||||
hasActiveFilter: boolean;
|
||||
schemasDisplayed: DBSchema[];
|
||||
@@ -45,4 +46,5 @@ export const diagramFilterContext = createContext<DiagramFilterContext>({
|
||||
schemasDisplayed: [],
|
||||
addTablesToFilter: emptyFn,
|
||||
removeTablesFromFilter: emptyFn,
|
||||
loading: false,
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { filterTable } from '@/lib/domain/diagram-filter/filter';
|
||||
import { schemaNameToSchemaId } from '@/lib/domain';
|
||||
import { databasesWithSchemas, schemaNameToSchemaId } from '@/lib/domain';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import type { ChartDBEvent } from '../chartdb-context/chartdb-context';
|
||||
|
||||
@@ -28,6 +28,7 @@ export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||
const { diagramId, tables, schemas, databaseType, events } = useChartDB();
|
||||
const { getDiagramFilter, updateDiagramFilter } = useStorage();
|
||||
const [filter, setFilter] = useState<DiagramFilter>({});
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
const allSchemasIds = useMemo(() => {
|
||||
return schemas.map((schema) => schema.id);
|
||||
@@ -62,11 +63,26 @@ export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const loadFilterFromStorage = async (diagramId: string) => {
|
||||
if (diagramId) {
|
||||
const storedFilter = await getDiagramFilter(diagramId);
|
||||
setFilter(storedFilter ?? {});
|
||||
|
||||
let filterToSet = storedFilter;
|
||||
|
||||
if (!filterToSet) {
|
||||
// If no filter is stored, set default based on database type
|
||||
filterToSet =
|
||||
schemas.length > 0
|
||||
? { schemaIds: [schemas[0].id] }
|
||||
: {};
|
||||
}
|
||||
|
||||
setFilter(filterToSet);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
setFilter({});
|
||||
@@ -75,7 +91,7 @@ export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||
loadFilterFromStorage(diagramId);
|
||||
diagramIdOfLoadedFilter.current = diagramId;
|
||||
}
|
||||
}, [diagramId, getDiagramFilter]);
|
||||
}, [diagramId, getDiagramFilter, schemas]);
|
||||
|
||||
const clearSchemaIds: DiagramFilterContext['clearSchemaIdsFilter'] =
|
||||
useCallback(() => {
|
||||
@@ -246,7 +262,7 @@ export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||
const toggleTableFilter: DiagramFilterContext['toggleTableFilter'] =
|
||||
useCallback(
|
||||
(tableId: string) => {
|
||||
if (!defaultSchemas[databaseType]) {
|
||||
if (!databasesWithSchemas.includes(databaseType)) {
|
||||
// No schemas, toggle table filter without schema context
|
||||
toggleTableFilterForNoSchema(tableId);
|
||||
return;
|
||||
@@ -500,6 +516,7 @@ export const DiagramFilterProvider: React.FC<React.PropsWithChildren> = ({
|
||||
events.useSubscription(eventConsumer);
|
||||
|
||||
const value: DiagramFilterContext = {
|
||||
loading,
|
||||
filter,
|
||||
clearSchemaIdsFilter: clearSchemaIds,
|
||||
setTableIdsFilterEmpty: setTableIdsEmpty,
|
||||
|
||||
@@ -245,7 +245,9 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
|
||||
const getDiagramFilter: StorageContext['getDiagramFilter'] = useCallback(
|
||||
async (diagramId: string): Promise<DiagramFilter | undefined> => {
|
||||
return await db.diagram_filters.get({ diagramId });
|
||||
const filter = await db.diagram_filters.get({ diagramId });
|
||||
|
||||
return filter;
|
||||
},
|
||||
[db]
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { SelectTables } from '../common/select-tables/select-tables';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||
import { sqlImportToDiagram } from '@/lib/data/sql-import';
|
||||
import { getInitialFilterForLargeDiagram } from '@/lib/export-import-utils';
|
||||
import type { SelectedTable } from '@/lib/data/import-metadata/filter-metadata';
|
||||
import { filterMetadataByTables } from '@/lib/data/import-metadata/filter-metadata';
|
||||
import { MAX_TABLES_WITHOUT_SHOWING_FILTER } from '../common/select-tables/constants';
|
||||
@@ -43,7 +44,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
const [step, setStep] = useState<CreateDiagramDialogStep>(
|
||||
CreateDiagramDialogStep.SELECT_DATABASE
|
||||
);
|
||||
const { listDiagrams, addDiagram } = useStorage();
|
||||
const { listDiagrams, addDiagram, updateDiagramFilter } = useStorage();
|
||||
const [diagramNumber, setDiagramNumber] = useState<number>(1);
|
||||
const navigate = useNavigate();
|
||||
const [parsedMetadata, setParsedMetadata] = useState<DatabaseMetadata>();
|
||||
@@ -89,6 +90,12 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
sourceDatabaseType: databaseType,
|
||||
targetDatabaseType: databaseType,
|
||||
});
|
||||
|
||||
// Check if we need a filter for large SQL imports
|
||||
const initialFilter = getInitialFilterForLargeDiagram(diagram);
|
||||
if (initialFilter) {
|
||||
await updateDiagramFilter(diagram.id, initialFilter);
|
||||
}
|
||||
} else {
|
||||
let metadata: DatabaseMetadata | undefined = databaseMetadata;
|
||||
|
||||
@@ -103,7 +110,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
diagram = await loadFromDatabaseMetadata({
|
||||
const result = await loadFromDatabaseMetadata({
|
||||
databaseType,
|
||||
databaseMetadata: metadata,
|
||||
diagramNumber,
|
||||
@@ -112,6 +119,12 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
? undefined
|
||||
: databaseEdition,
|
||||
});
|
||||
diagram = result.diagram;
|
||||
|
||||
// Apply filter if needed for large diagrams
|
||||
if (result.initialFilter) {
|
||||
await updateDiagramFilter(diagram.id, result.initialFilter);
|
||||
}
|
||||
}
|
||||
|
||||
await addDiagram({ diagram });
|
||||
@@ -126,6 +139,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
importMethod,
|
||||
databaseType,
|
||||
addDiagram,
|
||||
updateDiagramFilter,
|
||||
databaseEdition,
|
||||
closeCreateDiagramDialog,
|
||||
navigate,
|
||||
|
||||
@@ -218,8 +218,14 @@ export const CreateRelationshipDialog: React.FC<
|
||||
closeCreateRelationshipDialog();
|
||||
}
|
||||
}}
|
||||
modal={false}
|
||||
>
|
||||
<DialogContent className="flex flex-col overflow-y-auto" showClose>
|
||||
<DialogContent
|
||||
className="flex flex-col overflow-y-auto"
|
||||
showClose
|
||||
forceOverlay
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{t('create_relationship_dialog.title')}
|
||||
|
||||
@@ -69,7 +69,7 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
||||
const databaseMetadata: DatabaseMetadata =
|
||||
loadDatabaseMetadata(scriptResult);
|
||||
|
||||
diagram = await loadFromDatabaseMetadata({
|
||||
const result = await loadFromDatabaseMetadata({
|
||||
databaseType,
|
||||
databaseMetadata,
|
||||
databaseEdition:
|
||||
@@ -77,6 +77,9 @@ export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
||||
? undefined
|
||||
: databaseEdition,
|
||||
});
|
||||
diagram = result.diagram;
|
||||
// Note: For importing into existing diagram, we don't apply the filter
|
||||
// as it would affect the existing tables too
|
||||
}
|
||||
|
||||
const tableIdsToRemove = tables
|
||||
|
||||
@@ -16,7 +16,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FileUploader } from '@/components/file-uploader/file-uploader';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { diagramFromJSONInput } from '@/lib/export-import-utils';
|
||||
import {
|
||||
diagramFromJSONInput,
|
||||
getInitialFilterForLargeDiagram,
|
||||
} from '@/lib/export-import-utils';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/alert/alert';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
|
||||
@@ -27,7 +30,7 @@ export const ImportDiagramDialog: React.FC<ImportDiagramDialogProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const { addDiagram } = useStorage();
|
||||
const { addDiagram, updateDiagramFilter } = useStorage();
|
||||
const navigate = useNavigate();
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
@@ -58,8 +61,16 @@ export const ImportDiagramDialog: React.FC<ImportDiagramDialogProps> = ({
|
||||
try {
|
||||
const diagram = diagramFromJSONInput(json);
|
||||
|
||||
// Check if we need to apply a filter for large diagrams
|
||||
const initialFilter = getInitialFilterForLargeDiagram(diagram);
|
||||
|
||||
await addDiagram({ diagram });
|
||||
|
||||
// Apply the filter if needed (to hide isolated tables)
|
||||
if (initialFilter) {
|
||||
await updateDiagramFilter(diagram.id, initialFilter);
|
||||
}
|
||||
|
||||
closeImportDiagramDialog();
|
||||
closeCreateDiagramDialog();
|
||||
|
||||
@@ -74,6 +85,7 @@ export const ImportDiagramDialog: React.FC<ImportDiagramDialogProps> = ({
|
||||
}, [
|
||||
file,
|
||||
addDiagram,
|
||||
updateDiagramFilter,
|
||||
navigate,
|
||||
closeImportDiagramDialog,
|
||||
closeCreateDiagramDialog,
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ar: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'جديد',
|
||||
browse: 'تصفح',
|
||||
tables: 'الجداول',
|
||||
relationships: 'الروابط',
|
||||
areas: 'المناطق',
|
||||
dependencies: 'التبعيات',
|
||||
custom_types: 'الأنواع المخصصة',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ملف',
|
||||
new: 'جديد',
|
||||
open: 'فتح',
|
||||
databases: {
|
||||
databases: 'قواعد البيانات',
|
||||
new: 'مخطط جديد',
|
||||
browse: 'تصفح...',
|
||||
save: 'حفظ',
|
||||
import: 'استيراد قاعدة بيانات',
|
||||
export_sql: 'SQL تصدير',
|
||||
export_as: 'تصدير كـ',
|
||||
delete_diagram: 'حذف الرسم البياني',
|
||||
exit: 'خروج',
|
||||
},
|
||||
edit: {
|
||||
edit: 'تحرير',
|
||||
@@ -149,6 +157,7 @@ export const ar: LanguageTranslation = {
|
||||
title: 'خصائص الفهرس',
|
||||
name: 'الإسم',
|
||||
unique: 'فريد',
|
||||
index_type: 'نوع الفهرس',
|
||||
delete_index: 'حذف الفهرس',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const bn: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'নতুন',
|
||||
browse: 'ব্রাউজ',
|
||||
tables: 'টেবিল',
|
||||
relationships: 'সম্পর্ক',
|
||||
areas: 'এলাকা',
|
||||
dependencies: 'নির্ভরতা',
|
||||
custom_types: 'কাস্টম টাইপ',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ফাইল',
|
||||
new: 'নতুন',
|
||||
open: 'খুলুন',
|
||||
databases: {
|
||||
databases: 'ডাটাবেস',
|
||||
new: 'নতুন ডায়াগ্রাম',
|
||||
browse: 'ব্রাউজ করুন...',
|
||||
save: 'সংরক্ষণ করুন',
|
||||
import: 'ডাটাবেস আমদানি করুন',
|
||||
export_sql: 'SQL রপ্তানি করুন',
|
||||
export_as: 'রূপে রপ্তানি করুন',
|
||||
delete_diagram: 'ডায়াগ্রাম মুছুন',
|
||||
exit: 'প্রস্থান করুন',
|
||||
},
|
||||
edit: {
|
||||
edit: 'সম্পাদনা',
|
||||
@@ -151,6 +159,7 @@ export const bn: LanguageTranslation = {
|
||||
title: 'ইনডেক্স কর্ম',
|
||||
name: 'নাম',
|
||||
unique: 'অদ্বিতীয়',
|
||||
index_type: 'ইনডেক্স ধরন',
|
||||
delete_index: 'ইনডেক্স মুছুন',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const de: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Neu',
|
||||
browse: 'Durchsuchen',
|
||||
tables: 'Tabellen',
|
||||
relationships: 'Beziehungen',
|
||||
areas: 'Bereiche',
|
||||
dependencies: 'Abhängigkeiten',
|
||||
custom_types: 'Benutzerdefinierte Typen',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Datei',
|
||||
new: 'Neu',
|
||||
open: 'Öffnen',
|
||||
databases: {
|
||||
databases: 'Datenbanken',
|
||||
new: 'Neues Diagramm',
|
||||
browse: 'Durchsuchen...',
|
||||
save: 'Speichern',
|
||||
import: 'Datenbank importieren',
|
||||
export_sql: 'SQL exportieren',
|
||||
export_as: 'Exportieren als',
|
||||
delete_diagram: 'Diagramm löschen',
|
||||
exit: 'Beenden',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Bearbeiten',
|
||||
@@ -152,6 +160,7 @@ export const de: LanguageTranslation = {
|
||||
title: 'Indexattribute',
|
||||
name: 'Name',
|
||||
unique: 'Eindeutig',
|
||||
index_type: 'Indextyp',
|
||||
delete_index: 'Index löschen',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata } from '../types';
|
||||
|
||||
export const en = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'New',
|
||||
browse: 'Browse',
|
||||
tables: 'Tables',
|
||||
relationships: 'Refs',
|
||||
areas: 'Areas',
|
||||
dependencies: 'Dependencies',
|
||||
custom_types: 'Custom Types',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'File',
|
||||
new: 'New',
|
||||
open: 'Open',
|
||||
databases: {
|
||||
databases: 'Databases',
|
||||
new: 'New Diagram',
|
||||
browse: 'Browse...',
|
||||
save: 'Save',
|
||||
import: 'Import',
|
||||
export_sql: 'Export SQL',
|
||||
export_as: 'Export as',
|
||||
delete_diagram: 'Delete Diagram',
|
||||
exit: 'Exit',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Edit',
|
||||
@@ -145,6 +153,7 @@ export const en = {
|
||||
title: 'Index Attributes',
|
||||
name: 'Name',
|
||||
unique: 'Unique',
|
||||
index_type: 'Index Type',
|
||||
delete_index: 'Delete Index',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const es: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Nuevo',
|
||||
browse: 'Examinar',
|
||||
tables: 'Tablas',
|
||||
relationships: 'Relaciones',
|
||||
areas: 'Áreas',
|
||||
dependencies: 'Dependencias',
|
||||
custom_types: 'Tipos Personalizados',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Archivo',
|
||||
new: 'Nuevo',
|
||||
open: 'Abrir',
|
||||
databases: {
|
||||
databases: 'Bases de Datos',
|
||||
new: 'Nuevo Diagrama',
|
||||
browse: 'Examinar...',
|
||||
save: 'Guardar',
|
||||
import: 'Importar Base de Datos',
|
||||
export_sql: 'Exportar SQL',
|
||||
export_as: 'Exportar como',
|
||||
delete_diagram: 'Eliminar Diagrama',
|
||||
exit: 'Salir',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Editar',
|
||||
@@ -150,6 +158,7 @@ export const es: LanguageTranslation = {
|
||||
title: 'Atributos del Índice',
|
||||
name: 'Nombre',
|
||||
unique: 'Único',
|
||||
index_type: 'Tipo de Índice',
|
||||
delete_index: 'Eliminar Índice',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const fr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Nouveau',
|
||||
browse: 'Parcourir',
|
||||
tables: 'Tables',
|
||||
relationships: 'Relations',
|
||||
areas: 'Zones',
|
||||
dependencies: 'Dépendances',
|
||||
custom_types: 'Types Personnalisés',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Fichier',
|
||||
new: 'Nouveau',
|
||||
open: 'Ouvrir',
|
||||
databases: {
|
||||
databases: 'Bases de Données',
|
||||
new: 'Nouveau Diagramme',
|
||||
browse: 'Parcourir...',
|
||||
save: 'Enregistrer',
|
||||
import: 'Importer Base de Données',
|
||||
export_sql: 'Exporter SQL',
|
||||
export_as: 'Exporter en tant que',
|
||||
delete_diagram: 'Supprimer le Diagramme',
|
||||
exit: 'Quitter',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Édition',
|
||||
@@ -148,6 +156,7 @@ export const fr: LanguageTranslation = {
|
||||
title: "Attributs de l'Index",
|
||||
name: 'Nom',
|
||||
unique: 'Unique',
|
||||
index_type: "Type d'index",
|
||||
delete_index: "Supprimer l'Index",
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const gu: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'નવું',
|
||||
browse: 'બ્રાઉજ',
|
||||
tables: 'ટેબલો',
|
||||
relationships: 'સંબંધો',
|
||||
areas: 'ક્ષેત્રો',
|
||||
dependencies: 'નિર્ભરતાઓ',
|
||||
custom_types: 'કસ્ટમ ટાઇપ',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ફાઇલ',
|
||||
new: 'નવું',
|
||||
open: 'ખોલો',
|
||||
databases: {
|
||||
databases: 'ડેટાબેસેસ',
|
||||
new: 'નવું ડાયાગ્રામ',
|
||||
browse: 'બ્રાઉજ કરો...',
|
||||
save: 'સાચવો',
|
||||
import: 'ડેટાબેસ આયાત કરો',
|
||||
export_sql: 'SQL નિકાસ કરો',
|
||||
export_as: 'રૂપે નિકાસ કરો',
|
||||
delete_diagram: 'ડાયાગ્રામ કાઢી નાખો',
|
||||
exit: 'બહાર જાઓ',
|
||||
},
|
||||
edit: {
|
||||
edit: 'ફેરફાર',
|
||||
@@ -152,6 +160,7 @@ export const gu: LanguageTranslation = {
|
||||
title: 'ઇન્ડેક્સ લક્ષણો',
|
||||
name: 'નામ',
|
||||
unique: 'અદ્વિતીય',
|
||||
index_type: 'ઇન્ડેક્સ પ્રકાર',
|
||||
delete_index: 'ઇન્ડેક્સ કાઢી નાખો',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const hi: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'नया',
|
||||
browse: 'ब्राउज़',
|
||||
tables: 'टेबल',
|
||||
relationships: 'संबंध',
|
||||
areas: 'क्षेत्र',
|
||||
dependencies: 'निर्भरताएं',
|
||||
custom_types: 'कस्टम टाइप',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'फ़ाइल',
|
||||
new: 'नया',
|
||||
open: 'खोलें',
|
||||
databases: {
|
||||
databases: 'डेटाबेस',
|
||||
new: 'नया आरेख',
|
||||
browse: 'ब्राउज़ करें...',
|
||||
save: 'सहेजें',
|
||||
import: 'डेटाबेस आयात करें',
|
||||
export_sql: 'SQL निर्यात करें',
|
||||
export_as: 'के रूप में निर्यात करें',
|
||||
delete_diagram: 'आरेख हटाएँ',
|
||||
exit: 'बाहर जाएँ',
|
||||
},
|
||||
edit: {
|
||||
edit: 'संपादित करें',
|
||||
@@ -151,6 +159,7 @@ export const hi: LanguageTranslation = {
|
||||
title: 'सूचकांक विशेषताएँ',
|
||||
name: 'नाम',
|
||||
unique: 'अद्वितीय',
|
||||
index_type: 'इंडेक्स प्रकार',
|
||||
delete_index: 'सूचकांक हटाएँ',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const hr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Novi',
|
||||
browse: 'Pregledaj',
|
||||
tables: 'Tablice',
|
||||
relationships: 'Veze',
|
||||
areas: 'Područja',
|
||||
dependencies: 'Ovisnosti',
|
||||
custom_types: 'Prilagođeni Tipovi',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Datoteka',
|
||||
new: 'Nova',
|
||||
open: 'Otvori',
|
||||
databases: {
|
||||
databases: 'Baze Podataka',
|
||||
new: 'Novi Dijagram',
|
||||
browse: 'Pregledaj...',
|
||||
save: 'Spremi',
|
||||
import: 'Uvezi',
|
||||
export_sql: 'Izvezi SQL',
|
||||
export_as: 'Izvezi kao',
|
||||
delete_diagram: 'Izbriši dijagram',
|
||||
exit: 'Izađi',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Uredi',
|
||||
@@ -146,6 +154,7 @@ export const hr: LanguageTranslation = {
|
||||
title: 'Atributi indeksa',
|
||||
name: 'Naziv',
|
||||
unique: 'Jedinstven',
|
||||
index_type: 'Vrsta indeksa',
|
||||
delete_index: 'Izbriši indeks',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const id_ID: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Baru',
|
||||
browse: 'Jelajahi',
|
||||
tables: 'Tabel',
|
||||
relationships: 'Relasi',
|
||||
areas: 'Area',
|
||||
dependencies: 'Ketergantungan',
|
||||
custom_types: 'Tipe Kustom',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Berkas',
|
||||
new: 'Buat Baru',
|
||||
open: 'Buka',
|
||||
databases: {
|
||||
databases: 'Basis Data',
|
||||
new: 'Diagram Baru',
|
||||
browse: 'Jelajahi...',
|
||||
save: 'Simpan',
|
||||
import: 'Impor Database',
|
||||
export_sql: 'Ekspor SQL',
|
||||
export_as: 'Ekspor Sebagai',
|
||||
delete_diagram: 'Hapus Diagram',
|
||||
exit: 'Keluar',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Ubah',
|
||||
@@ -150,6 +158,7 @@ export const id_ID: LanguageTranslation = {
|
||||
title: 'Atribut Indeks',
|
||||
name: 'Nama',
|
||||
unique: 'Unik',
|
||||
index_type: 'Tipe Indeks',
|
||||
delete_index: 'Hapus Indeks',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ja: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '新規',
|
||||
browse: '参照',
|
||||
tables: 'テーブル',
|
||||
relationships: 'リレーション',
|
||||
areas: 'エリア',
|
||||
dependencies: '依存関係',
|
||||
custom_types: 'カスタムタイプ',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ファイル',
|
||||
new: '新規',
|
||||
open: '開く',
|
||||
databases: {
|
||||
databases: 'データベース',
|
||||
new: '新しいダイアグラム',
|
||||
browse: '参照...',
|
||||
save: '保存',
|
||||
import: 'データベースをインポート',
|
||||
export_sql: 'SQLをエクスポート',
|
||||
export_as: '形式を指定してエクスポート',
|
||||
delete_diagram: 'ダイアグラムを削除',
|
||||
exit: '終了',
|
||||
},
|
||||
edit: {
|
||||
edit: '編集',
|
||||
@@ -154,6 +162,7 @@ export const ja: LanguageTranslation = {
|
||||
title: 'インデックス属性',
|
||||
name: '名前',
|
||||
unique: 'ユニーク',
|
||||
index_type: 'インデックスタイプ',
|
||||
delete_index: 'インデックスを削除',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ko_KR: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '새로 만들기',
|
||||
browse: '찾아보기',
|
||||
tables: '테이블',
|
||||
relationships: '관계',
|
||||
areas: '영역',
|
||||
dependencies: '종속성',
|
||||
custom_types: '사용자 지정 타입',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: '파일',
|
||||
databases: {
|
||||
databases: '데이터베이스',
|
||||
new: '새 다이어그램',
|
||||
open: '열기',
|
||||
browse: '찾아보기...',
|
||||
save: '저장',
|
||||
import: '데이터베이스 가져오기',
|
||||
export_sql: 'SQL로 저장',
|
||||
export_as: '다른 형식으로 저장',
|
||||
delete_diagram: '다이어그램 삭제',
|
||||
exit: '종료',
|
||||
},
|
||||
edit: {
|
||||
edit: '편집',
|
||||
@@ -150,6 +158,7 @@ export const ko_KR: LanguageTranslation = {
|
||||
title: '인덱스 속성',
|
||||
name: '인덱스 명',
|
||||
unique: '유니크 여부',
|
||||
index_type: '인덱스 타입',
|
||||
delete_index: '인덱스 삭제',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const mr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'नवीन',
|
||||
browse: 'ब्राउज',
|
||||
tables: 'टेबल',
|
||||
relationships: 'संबंध',
|
||||
areas: 'क्षेत्रे',
|
||||
dependencies: 'अवलंबने',
|
||||
custom_types: 'कस्टम प्रकार',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'फाइल',
|
||||
new: 'नवीन',
|
||||
open: 'उघडा',
|
||||
databases: {
|
||||
databases: 'डेटाबेस',
|
||||
new: 'नवीन आरेख',
|
||||
browse: 'ब्राउज करा...',
|
||||
save: 'जतन करा',
|
||||
import: 'डेटाबेस इम्पोर्ट करा',
|
||||
export_sql: 'SQL एक्स्पोर्ट करा',
|
||||
export_as: 'म्हणून एक्स्पोर्ट करा',
|
||||
delete_diagram: 'आरेख हटवा',
|
||||
exit: 'बाहेर पडा',
|
||||
},
|
||||
edit: {
|
||||
edit: 'संपादन करा',
|
||||
@@ -153,6 +161,7 @@ export const mr: LanguageTranslation = {
|
||||
title: 'इंडेक्स गुणधर्म',
|
||||
name: 'नाव',
|
||||
unique: 'युनिक',
|
||||
index_type: 'इंडेक्स प्रकार',
|
||||
delete_index: 'इंडेक्स हटवा',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ne: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'नयाँ',
|
||||
browse: 'ब्राउज',
|
||||
tables: 'टेबलहरू',
|
||||
relationships: 'सम्बन्धहरू',
|
||||
areas: 'क्षेत्रहरू',
|
||||
dependencies: 'निर्भरताहरू',
|
||||
custom_types: 'कस्टम प्रकारहरू',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'फाइल',
|
||||
new: 'नयाँ',
|
||||
open: 'खोल्नुहोस्',
|
||||
databases: {
|
||||
databases: 'डाटाबेसहरू',
|
||||
new: 'नयाँ डायाग्राम',
|
||||
browse: 'ब्राउज गर्नुहोस्...',
|
||||
save: 'सुरक्षित गर्नुहोस्',
|
||||
import: 'डाटाबेस आयात गर्नुहोस्',
|
||||
export_sql: 'SQL निर्यात गर्नुहोस्',
|
||||
export_as: 'निर्यात गर्नुहोस्',
|
||||
delete_diagram: 'डायाग्राम हटाउनुहोस्',
|
||||
exit: 'बाहिर निस्कनुहोस्',
|
||||
},
|
||||
edit: {
|
||||
edit: 'सम्पादन',
|
||||
@@ -151,6 +159,7 @@ export const ne: LanguageTranslation = {
|
||||
title: 'सूचक विशेषताहरू',
|
||||
name: 'नाम',
|
||||
unique: 'अनन्य',
|
||||
index_type: 'इन्डेक्स प्रकार',
|
||||
delete_index: 'सूचक हटाउनुहोस्',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const pt_BR: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Novo',
|
||||
browse: 'Navegar',
|
||||
tables: 'Tabelas',
|
||||
relationships: 'Relacionamentos',
|
||||
areas: 'Áreas',
|
||||
dependencies: 'Dependências',
|
||||
custom_types: 'Tipos Personalizados',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Arquivo',
|
||||
new: 'Novo',
|
||||
open: 'Abrir',
|
||||
databases: {
|
||||
databases: 'Bancos de Dados',
|
||||
new: 'Novo Diagrama',
|
||||
browse: 'Navegar...',
|
||||
save: 'Salvar',
|
||||
import: 'Importar Banco de Dados',
|
||||
export_sql: 'Exportar SQL',
|
||||
export_as: 'Exportar como',
|
||||
delete_diagram: 'Excluir Diagrama',
|
||||
exit: 'Sair',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Editar',
|
||||
@@ -151,6 +159,7 @@ export const pt_BR: LanguageTranslation = {
|
||||
title: 'Atributos do Índice',
|
||||
name: 'Nome',
|
||||
unique: 'Único',
|
||||
index_type: 'Tipo de Índice',
|
||||
delete_index: 'Excluir Índice',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const ru: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Новая',
|
||||
browse: 'Обзор',
|
||||
tables: 'Таблицы',
|
||||
relationships: 'Связи',
|
||||
areas: 'Области',
|
||||
dependencies: 'Зависимости',
|
||||
custom_types: 'Пользовательские типы',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Файл',
|
||||
new: 'Создать',
|
||||
open: 'Открыть',
|
||||
databases: {
|
||||
databases: 'Базы данных',
|
||||
new: 'Новая диаграмма',
|
||||
browse: 'Обзор...',
|
||||
save: 'Сохранить',
|
||||
import: 'Импортировать базу данных',
|
||||
export_sql: 'Экспорт SQL',
|
||||
export_as: 'Экспортировать как',
|
||||
delete_diagram: 'Удалить диаграмму',
|
||||
exit: 'Выход',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Изменение',
|
||||
@@ -147,6 +155,7 @@ export const ru: LanguageTranslation = {
|
||||
title: 'Атрибуты индекса',
|
||||
name: 'Имя',
|
||||
unique: 'Уникальный',
|
||||
index_type: 'Тип индекса',
|
||||
delete_index: 'Удалить индекс',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const te: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'కొత్తది',
|
||||
browse: 'బ్రాఉజ్',
|
||||
tables: 'టేబల్లు',
|
||||
relationships: 'సంబంధాలు',
|
||||
areas: 'ప్రదేశాలు',
|
||||
dependencies: 'ఆధారతలు',
|
||||
custom_types: 'కస్టమ్ టైప్స్',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'ఫైల్',
|
||||
new: 'కొత్తది',
|
||||
open: 'తెరవు',
|
||||
databases: {
|
||||
databases: 'డేటాబేస్లు',
|
||||
new: 'కొత్త డైగ్రాం',
|
||||
browse: 'బ్రాఉజ్ చేయండి...',
|
||||
save: 'సేవ్',
|
||||
import: 'డేటాబేస్ను దిగుమతి చేసుకోండి',
|
||||
export_sql: 'SQL ఎగుమతి',
|
||||
export_as: 'వగా ఎగుమతి చేయండి',
|
||||
delete_diagram: 'చిత్రాన్ని తొలగించండి',
|
||||
exit: 'నిష్క్రమించు',
|
||||
},
|
||||
edit: {
|
||||
edit: 'సవరించు',
|
||||
@@ -151,6 +159,7 @@ export const te: LanguageTranslation = {
|
||||
title: 'ఇండెక్స్ గుణాలు',
|
||||
name: 'పేరు',
|
||||
unique: 'అద్వితీయ',
|
||||
index_type: 'ఇండెక్స్ రకం',
|
||||
delete_index: 'ఇండెక్స్ తొలగించు',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const tr: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Yeni',
|
||||
browse: 'Gözat',
|
||||
tables: 'Tablolar',
|
||||
relationships: 'İlişkiler',
|
||||
areas: 'Alanlar',
|
||||
dependencies: 'Bağımlılıklar',
|
||||
custom_types: 'Özel Tipler',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Dosya',
|
||||
new: 'Yeni',
|
||||
open: 'Aç',
|
||||
databases: {
|
||||
databases: 'Veritabanları',
|
||||
new: 'Yeni Diyagram',
|
||||
browse: 'Gözat...',
|
||||
save: 'Kaydet',
|
||||
import: 'Veritabanı İçe Aktar',
|
||||
export_sql: 'SQL Olarak Dışa Aktar',
|
||||
export_as: 'Olarak Dışa Aktar',
|
||||
delete_diagram: 'Diyagramı Sil',
|
||||
exit: 'Çıkış',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Düzenle',
|
||||
@@ -150,6 +158,7 @@ export const tr: LanguageTranslation = {
|
||||
title: 'İndeks Özellikleri',
|
||||
name: 'Ad',
|
||||
unique: 'Tekil',
|
||||
index_type: 'İndeks Türü',
|
||||
delete_index: 'İndeksi Sil',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const uk: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Нова',
|
||||
browse: 'Огляд',
|
||||
tables: 'Таблиці',
|
||||
relationships: 'Зв’язки',
|
||||
areas: 'Області',
|
||||
dependencies: 'Залежності',
|
||||
custom_types: 'Користувацькі типи',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Файл',
|
||||
new: 'Новий',
|
||||
open: 'Відкрити',
|
||||
databases: {
|
||||
databases: 'Бази даних',
|
||||
new: 'Нова діаграма',
|
||||
browse: 'Огляд...',
|
||||
save: 'Зберегти',
|
||||
import: 'Імпорт бази даних',
|
||||
export_sql: 'Експорт SQL',
|
||||
export_as: 'Експортувати як',
|
||||
delete_diagram: 'Видалити діаграму',
|
||||
exit: 'Вийти',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Редагувати',
|
||||
@@ -149,6 +157,7 @@ export const uk: LanguageTranslation = {
|
||||
title: 'Атрибути індексу',
|
||||
name: 'Назва індекса',
|
||||
unique: 'Унікальний',
|
||||
index_type: 'Тип індексу',
|
||||
delete_index: 'Видалити індекс',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const vi: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: 'Mới',
|
||||
browse: 'Duyệt',
|
||||
tables: 'Bảng',
|
||||
relationships: 'Mối quan hệ',
|
||||
areas: 'Khu vực',
|
||||
dependencies: 'Phụ thuộc',
|
||||
custom_types: 'Kiểu tùy chỉnh',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: 'Tệp',
|
||||
new: 'Tạo mới',
|
||||
open: 'Mở',
|
||||
databases: {
|
||||
databases: 'Cơ sở dữ liệu',
|
||||
new: 'Sơ đồ mới',
|
||||
browse: 'Duyệt...',
|
||||
save: 'Lưu',
|
||||
import: 'Nhập cơ sở dữ liệu',
|
||||
export_sql: 'Xuất SQL',
|
||||
export_as: 'Xuất thành',
|
||||
delete_diagram: 'Xóa sơ đồ',
|
||||
exit: 'Thoát',
|
||||
},
|
||||
edit: {
|
||||
edit: 'Sửa',
|
||||
@@ -150,6 +158,7 @@ export const vi: LanguageTranslation = {
|
||||
title: 'Thuộc tính chỉ mục',
|
||||
name: 'Tên',
|
||||
unique: 'Giá trị duy nhất',
|
||||
index_type: 'Loại chỉ mục',
|
||||
delete_index: 'Xóa chỉ mục',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const zh_CN: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '新建',
|
||||
browse: '浏览',
|
||||
tables: '表',
|
||||
relationships: '关系',
|
||||
areas: '区域',
|
||||
dependencies: '依赖关系',
|
||||
custom_types: '自定义类型',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: '文件',
|
||||
new: '新建',
|
||||
open: '打开',
|
||||
databases: {
|
||||
databases: '数据库',
|
||||
new: '新建关系图',
|
||||
browse: '浏览...',
|
||||
save: '保存',
|
||||
import: '导入数据库',
|
||||
export_sql: '导出 SQL 语句',
|
||||
export_as: '导出为',
|
||||
delete_diagram: '删除关系图',
|
||||
exit: '退出',
|
||||
},
|
||||
edit: {
|
||||
edit: '编辑',
|
||||
@@ -147,6 +155,7 @@ export const zh_CN: LanguageTranslation = {
|
||||
title: '索引属性',
|
||||
name: '名称',
|
||||
unique: '唯一',
|
||||
index_type: '索引类型',
|
||||
delete_index: '删除索引',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -2,17 +2,25 @@ import type { LanguageMetadata, LanguageTranslation } from '../types';
|
||||
|
||||
export const zh_TW: LanguageTranslation = {
|
||||
translation: {
|
||||
editor_sidebar: {
|
||||
new_diagram: '新建',
|
||||
browse: '瀏覽',
|
||||
tables: '表格',
|
||||
relationships: '關係',
|
||||
areas: '區域',
|
||||
dependencies: '相依性',
|
||||
custom_types: '自定義類型',
|
||||
},
|
||||
menu: {
|
||||
file: {
|
||||
file: '檔案',
|
||||
new: '新增',
|
||||
open: '開啟',
|
||||
databases: {
|
||||
databases: '資料庫',
|
||||
new: '新增圖表',
|
||||
browse: '瀏覽...',
|
||||
save: '儲存',
|
||||
import: '匯入資料庫',
|
||||
export_sql: '匯出 SQL',
|
||||
export_as: '匯出為特定格式',
|
||||
delete_diagram: '刪除圖表',
|
||||
exit: '退出',
|
||||
},
|
||||
edit: {
|
||||
edit: '編輯',
|
||||
@@ -147,6 +155,7 @@ export const zh_TW: LanguageTranslation = {
|
||||
title: '索引屬性',
|
||||
name: '名稱',
|
||||
unique: '唯一',
|
||||
index_type: '索引類型',
|
||||
delete_index: '刪除索引',
|
||||
},
|
||||
table_actions: {
|
||||
|
||||
@@ -19,3 +19,5 @@ export const randomColor = () => {
|
||||
|
||||
export const viewColor = '#b0b0b0';
|
||||
export const materializedViewColor = '#7d7d7d';
|
||||
export const defaultTableColor = '#8eb7ff';
|
||||
export const defaultAreaColor = '#b067e9';
|
||||
|
||||
@@ -405,7 +405,7 @@ export function exportPostgreSQL({
|
||||
.filter(Boolean);
|
||||
|
||||
return indexFieldNames.length > 0
|
||||
? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName} (${indexFieldNames.join(', ')});`
|
||||
? `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName}${index.type && index.type !== 'btree' ? ` USING ${index.type.toUpperCase()}` : ''} (${indexFieldNames.join(', ')});`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBIndex } from '@/lib/domain/db-index';
|
||||
import type { DataType } from '@/lib/data/data-types/data-types';
|
||||
import { genericDataTypes } from '@/lib/data/data-types/generic-data-types';
|
||||
import { randomColor } from '@/lib/colors';
|
||||
import { defaultTableColor } from '@/lib/colors';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||
import { DBCustomTypeKind } from '@/lib/domain/db-custom-type';
|
||||
@@ -727,10 +727,10 @@ export function convertToChartDBDiagram(
|
||||
indexes,
|
||||
x: col * tableSpacing,
|
||||
y: row * tableSpacing,
|
||||
color: randomColor(),
|
||||
color: defaultTableColor,
|
||||
isView: false,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
} satisfies DBTable;
|
||||
});
|
||||
|
||||
// Process relationships
|
||||
|
||||
@@ -220,11 +220,45 @@ export async function sqlImportToDiagram({
|
||||
targetDatabaseType
|
||||
);
|
||||
|
||||
const adjustedTables = adjustTablePositions({
|
||||
tables: diagram.tables ?? [],
|
||||
relationships: diagram.relationships ?? [],
|
||||
mode: 'perSchema',
|
||||
});
|
||||
// Apply the same logic as loadFromDatabaseMetadata for large diagrams
|
||||
const LARGE_DIAGRAM_THRESHOLD = 200;
|
||||
const tables = diagram.tables ?? [];
|
||||
const relationships = diagram.relationships ?? [];
|
||||
let adjustedTables = tables;
|
||||
|
||||
if (tables.length > LARGE_DIAGRAM_THRESHOLD) {
|
||||
// Create a set of table IDs that have relationships
|
||||
const tablesWithRelationships = new Set<string>();
|
||||
relationships.forEach((rel) => {
|
||||
tablesWithRelationships.add(rel.sourceTableId);
|
||||
tablesWithRelationships.add(rel.targetTableId);
|
||||
});
|
||||
|
||||
// Separate tables into connected and isolated
|
||||
const connectedTables = tables.filter((table) =>
|
||||
tablesWithRelationships.has(table.id)
|
||||
);
|
||||
const isolatedTables = tables.filter(
|
||||
(table) => !tablesWithRelationships.has(table.id)
|
||||
);
|
||||
|
||||
// Only reorder connected tables
|
||||
const reorderedConnectedTables = adjustTablePositions({
|
||||
tables: connectedTables,
|
||||
relationships,
|
||||
mode: 'perSchema',
|
||||
});
|
||||
|
||||
// Combine reordered connected tables with isolated tables
|
||||
adjustedTables = [...reorderedConnectedTables, ...isolatedTables];
|
||||
} else {
|
||||
// For smaller diagrams, reorder all tables as before
|
||||
adjustedTables = adjustTablePositions({
|
||||
tables,
|
||||
relationships,
|
||||
mode: 'perSchema',
|
||||
});
|
||||
}
|
||||
|
||||
const sortedTables = adjustedTables.sort((a, b) => {
|
||||
if (a.isView === b.isView) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Cardinality, DBRelationship } from '@/lib/domain/db-relationship';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DataTypeData } from '@/lib/data/data-types/data-types';
|
||||
import { findDataTypeDataById } from '@/lib/data/data-types/data-types';
|
||||
import { randomColor } from '@/lib/colors';
|
||||
import { defaultTableColor } from '@/lib/colors';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import type Field from '@dbml/core/types/model_structure/field';
|
||||
import type { DBIndex } from '@/lib/domain';
|
||||
@@ -507,11 +507,11 @@ export const importDBMLToDiagram = async (
|
||||
indexes,
|
||||
x: col * tableSpacing,
|
||||
y: row * tableSpacing,
|
||||
color: randomColor(),
|
||||
color: defaultTableColor,
|
||||
isView: false,
|
||||
createdAt: Date.now(),
|
||||
comments: tableComment,
|
||||
} as DBTable;
|
||||
} satisfies DBTable;
|
||||
});
|
||||
|
||||
// Create relationships using the refs
|
||||
|
||||
@@ -2,6 +2,25 @@ import { z } from 'zod';
|
||||
import type { AggregatedIndexInfo } from '../data/import-metadata/metadata-types/index-info';
|
||||
import { generateId } from '../utils';
|
||||
import type { DBField } from './db-field';
|
||||
import { DatabaseType } from './database-type';
|
||||
|
||||
export const INDEX_TYPES = [
|
||||
'btree',
|
||||
'hash',
|
||||
'gist',
|
||||
'gin',
|
||||
'spgist',
|
||||
'brin',
|
||||
// sql server
|
||||
'nonclustered',
|
||||
'clustered',
|
||||
'xml',
|
||||
'fulltext',
|
||||
'spatial',
|
||||
'hash',
|
||||
'index',
|
||||
] as const;
|
||||
export type IndexType = (typeof INDEX_TYPES)[number];
|
||||
|
||||
export interface DBIndex {
|
||||
id: string;
|
||||
@@ -9,6 +28,7 @@ export interface DBIndex {
|
||||
unique: boolean;
|
||||
fieldIds: string[];
|
||||
createdAt: number;
|
||||
type?: IndexType | null;
|
||||
}
|
||||
|
||||
export const dbIndexSchema: z.ZodType<DBIndex> = z.object({
|
||||
@@ -17,6 +37,7 @@ export const dbIndexSchema: z.ZodType<DBIndex> = z.object({
|
||||
unique: z.boolean(),
|
||||
fieldIds: z.array(z.string()),
|
||||
createdAt: z.number(),
|
||||
type: z.enum(INDEX_TYPES).optional(),
|
||||
});
|
||||
|
||||
export const createIndexesFromMetadata = ({
|
||||
@@ -36,5 +57,10 @@ export const createIndexesFromMetadata = ({
|
||||
.map((c) => fields.find((f) => f.name === c.name)?.id)
|
||||
.filter((id): id is string => id !== undefined),
|
||||
createdAt: Date.now(),
|
||||
type: idx.index_type?.toLowerCase() as IndexType,
|
||||
})
|
||||
);
|
||||
|
||||
export const databaseIndexTypes: { [key in DatabaseType]?: IndexType[] } = {
|
||||
[DatabaseType.POSTGRESQL]: ['btree', 'hash'],
|
||||
};
|
||||
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
} from './db-field';
|
||||
import type { TableInfo } from '../data/import-metadata/metadata-types/table-info';
|
||||
import { createAggregatedIndexes } from '../data/import-metadata/metadata-types/index-info';
|
||||
import { materializedViewColor, viewColor, randomColor } from '@/lib/colors';
|
||||
import {
|
||||
materializedViewColor,
|
||||
viewColor,
|
||||
defaultTableColor,
|
||||
} from '@/lib/colors';
|
||||
import type { DBRelationship } from './db-relationship';
|
||||
import {
|
||||
decodeBase64ToUtf16LE,
|
||||
@@ -22,6 +26,7 @@ import { schemaNameToDomainSchemaName } from './db-schema';
|
||||
import { DatabaseType } from './database-type';
|
||||
import type { DatabaseMetadata } from '../data/import-metadata/metadata-types/database-metadata';
|
||||
import { z } from 'zod';
|
||||
import type { Area } from './area';
|
||||
|
||||
export const MAX_TABLE_SIZE = 450;
|
||||
export const MID_TABLE_SIZE = 337;
|
||||
@@ -224,7 +229,7 @@ export const createTablesFromMetadata = ({
|
||||
? materializedViewColor
|
||||
: isView
|
||||
? viewColor
|
||||
: randomColor(),
|
||||
: defaultTableColor,
|
||||
isView: isView,
|
||||
isMaterializedView: isMaterializedView,
|
||||
createdAt: Date.now(),
|
||||
@@ -235,89 +240,170 @@ export const createTablesFromMetadata = ({
|
||||
return result;
|
||||
};
|
||||
|
||||
// Simple grid-based positioning for large databases
|
||||
const adjustTablePositionsSimple = (
|
||||
tables: DBTable[],
|
||||
mode: 'all' | 'perSchema' = 'all'
|
||||
): DBTable[] => {
|
||||
const TABLES_PER_ROW = 20;
|
||||
const TABLE_WIDTH = 250;
|
||||
const TABLE_HEIGHT = 350;
|
||||
const GAP_X = 50;
|
||||
const GAP_Y = 50;
|
||||
const START_X = 100;
|
||||
const START_Y = 100;
|
||||
|
||||
if (mode === 'perSchema') {
|
||||
// Group tables by schema for better organization
|
||||
const tablesBySchema = new Map<string, DBTable[]>();
|
||||
tables.forEach((table) => {
|
||||
const schema = table.schema || 'default';
|
||||
if (!tablesBySchema.has(schema)) {
|
||||
tablesBySchema.set(schema, []);
|
||||
}
|
||||
tablesBySchema.get(schema)!.push(table);
|
||||
});
|
||||
|
||||
const result: DBTable[] = [];
|
||||
let currentSchemaOffset = 0;
|
||||
|
||||
// Position each schema's tables in its own section
|
||||
tablesBySchema.forEach((schemaTables) => {
|
||||
schemaTables.forEach((table, index) => {
|
||||
const row = Math.floor(index / TABLES_PER_ROW);
|
||||
const col = index % TABLES_PER_ROW;
|
||||
|
||||
result.push({
|
||||
...table,
|
||||
x: START_X + col * (TABLE_WIDTH + GAP_X),
|
||||
y:
|
||||
START_Y +
|
||||
currentSchemaOffset +
|
||||
row * (TABLE_HEIGHT + GAP_Y),
|
||||
});
|
||||
});
|
||||
|
||||
// Add extra spacing between schemas
|
||||
const schemaRows = Math.ceil(schemaTables.length / TABLES_PER_ROW);
|
||||
currentSchemaOffset += schemaRows * (TABLE_HEIGHT + GAP_Y) + 200;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Simple mode - just arrange all tables in a grid
|
||||
return tables.map((table, index) => {
|
||||
const row = Math.floor(index / TABLES_PER_ROW);
|
||||
const col = index % TABLES_PER_ROW;
|
||||
|
||||
return {
|
||||
...table,
|
||||
x: START_X + col * (TABLE_WIDTH + GAP_X),
|
||||
y: START_Y + row * (TABLE_HEIGHT + GAP_Y),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const adjustTablePositions = ({
|
||||
relationships: inputRelationships,
|
||||
tables: inputTables,
|
||||
areas: inputAreas = [],
|
||||
mode = 'all',
|
||||
}: {
|
||||
tables: DBTable[];
|
||||
relationships: DBRelationship[];
|
||||
areas?: Area[];
|
||||
mode?: 'all' | 'perSchema';
|
||||
}): DBTable[] => {
|
||||
// For large databases, use simple grid layout for better performance
|
||||
if (inputTables.length > 200) {
|
||||
const result = adjustTablePositionsSimple(inputTables, mode);
|
||||
return result;
|
||||
}
|
||||
|
||||
// For smaller databases, use the existing complex algorithm
|
||||
// Deep copy inputs for manipulation
|
||||
const tables = deepCopy(inputTables);
|
||||
const relationships = deepCopy(inputRelationships);
|
||||
const areas = deepCopy(inputAreas);
|
||||
|
||||
// If there are no areas, fall back to the original algorithm
|
||||
if (areas.length === 0) {
|
||||
return adjustTablePositionsWithoutAreas(tables, relationships, mode);
|
||||
}
|
||||
|
||||
// Group tables by their parent area
|
||||
const tablesByArea = new Map<string | null, DBTable[]>();
|
||||
|
||||
// Initialize with empty arrays for all areas
|
||||
areas.forEach((area) => {
|
||||
tablesByArea.set(area.id, []);
|
||||
});
|
||||
|
||||
// Also create a group for tables without areas
|
||||
tablesByArea.set(null, []);
|
||||
|
||||
// Group tables
|
||||
tables.forEach((table) => {
|
||||
const areaId = table.parentAreaId || null;
|
||||
if (areaId && tablesByArea.has(areaId)) {
|
||||
tablesByArea.get(areaId)!.push(table);
|
||||
} else {
|
||||
// If the area doesn't exist or table has no area, put it in the null group
|
||||
tablesByArea.get(null)!.push(table);
|
||||
}
|
||||
});
|
||||
|
||||
// Check and adjust tables within each area
|
||||
areas.forEach((area) => {
|
||||
const tablesInArea = tablesByArea.get(area.id) || [];
|
||||
if (tablesInArea.length === 0) return;
|
||||
|
||||
// Only reposition tables that are outside their area bounds
|
||||
const tablesToReposition = tablesInArea.filter((table) => {
|
||||
return !isTableInsideArea(table, area);
|
||||
});
|
||||
|
||||
if (tablesToReposition.length > 0) {
|
||||
// Create a sub-graph of relationships for tables that need repositioning
|
||||
const areaRelationships = relationships.filter((rel) => {
|
||||
const sourceNeedsReposition = tablesToReposition.some(
|
||||
(t) => t.id === rel.sourceTableId
|
||||
);
|
||||
const targetNeedsReposition = tablesToReposition.some(
|
||||
(t) => t.id === rel.targetTableId
|
||||
);
|
||||
return sourceNeedsReposition && targetNeedsReposition;
|
||||
});
|
||||
|
||||
// Position only tables that are outside the area bounds
|
||||
positionTablesWithinArea(
|
||||
tablesToReposition,
|
||||
areaRelationships,
|
||||
area
|
||||
);
|
||||
}
|
||||
// Tables already inside the area keep their positions
|
||||
});
|
||||
|
||||
// Position free tables (those not in any area)
|
||||
const freeTables = tablesByArea.get(null) || [];
|
||||
if (freeTables.length > 0) {
|
||||
// Create a sub-graph of relationships for free tables
|
||||
const freeRelationships = relationships.filter((rel) => {
|
||||
const sourceIsFree = freeTables.some(
|
||||
(t) => t.id === rel.sourceTableId
|
||||
);
|
||||
const targetIsFree = freeTables.some(
|
||||
(t) => t.id === rel.targetTableId
|
||||
);
|
||||
return sourceIsFree && targetIsFree;
|
||||
});
|
||||
|
||||
// Use the original algorithm for free tables with area avoidance
|
||||
adjustTablePositionsWithoutAreas(
|
||||
freeTables,
|
||||
freeRelationships,
|
||||
mode,
|
||||
areas
|
||||
);
|
||||
}
|
||||
|
||||
return tables;
|
||||
};
|
||||
|
||||
// Helper function to check if a table is inside an area
|
||||
function isTableInsideArea(table: DBTable, area: Area): boolean {
|
||||
const tableDimensions = getTableDimensions(table);
|
||||
const padding = 20; // Same padding as used in positioning
|
||||
|
||||
return (
|
||||
table.x >= area.x + padding &&
|
||||
table.x + tableDimensions.width <= area.x + area.width - padding &&
|
||||
table.y >= area.y + padding &&
|
||||
table.y + tableDimensions.height <= area.y + area.height - padding
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to position tables within an area
|
||||
function positionTablesWithinArea(
|
||||
tables: DBTable[],
|
||||
_relationships: DBRelationship[],
|
||||
area: Area
|
||||
) {
|
||||
if (tables.length === 0) return;
|
||||
|
||||
const padding = 20; // Padding from area edges
|
||||
const gapX = 50;
|
||||
const gapY = 50;
|
||||
|
||||
// Available space within the area
|
||||
const availableWidth = area.width - 2 * padding;
|
||||
const availableHeight = area.height - 2 * padding;
|
||||
|
||||
// Simple grid layout within the area
|
||||
const cols = Math.max(1, Math.floor(availableWidth / 250));
|
||||
const rows = Math.ceil(tables.length / cols);
|
||||
|
||||
const cellWidth = availableWidth / cols;
|
||||
const cellHeight = availableHeight / Math.max(rows, 1);
|
||||
|
||||
tables.forEach((table, index) => {
|
||||
const col = index % cols;
|
||||
const row = Math.floor(index / cols);
|
||||
|
||||
// Position relative to area
|
||||
table.x = area.x + padding + col * cellWidth + gapX / 2;
|
||||
table.y = area.y + padding + row * cellHeight + gapY / 2;
|
||||
|
||||
// Ensure table stays within area bounds
|
||||
const tableDimensions = getTableDimensions(table);
|
||||
const maxX = area.x + area.width - padding - tableDimensions.width;
|
||||
const maxY = area.y + area.height - padding - tableDimensions.height;
|
||||
|
||||
table.x = Math.min(table.x, maxX);
|
||||
table.y = Math.min(table.y, maxY);
|
||||
table.x = Math.max(table.x, area.x + padding);
|
||||
table.y = Math.max(table.y, area.y + padding);
|
||||
});
|
||||
}
|
||||
|
||||
// Original algorithm with area avoidance
|
||||
function adjustTablePositionsWithoutAreas(
|
||||
tables: DBTable[],
|
||||
relationships: DBRelationship[],
|
||||
mode: 'all' | 'perSchema',
|
||||
areas: Area[] = []
|
||||
): DBTable[] {
|
||||
const adjustPositionsForTables = (tablesToAdjust: DBTable[]) => {
|
||||
const defaultTableWidth = 200;
|
||||
const defaultTableHeight = 300;
|
||||
@@ -339,8 +425,23 @@ export const adjustTablePositions = ({
|
||||
tableConnections.get(rel.targetTableId)!.add(rel.sourceTableId);
|
||||
});
|
||||
|
||||
// Sort tables by number of connections
|
||||
const sortedTables = [...tablesToAdjust].sort(
|
||||
// Separate tables into connected and isolated
|
||||
const connectedTables: DBTable[] = [];
|
||||
const isolatedTables: DBTable[] = [];
|
||||
|
||||
tablesToAdjust.forEach((table) => {
|
||||
if (
|
||||
tableConnections.has(table.id) &&
|
||||
tableConnections.get(table.id)!.size > 0
|
||||
) {
|
||||
connectedTables.push(table);
|
||||
} else {
|
||||
isolatedTables.push(table);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort connected tables by number of connections (most connected first)
|
||||
connectedTables.sort(
|
||||
(a, b) =>
|
||||
(tableConnections.get(b.id)?.size || 0) -
|
||||
(tableConnections.get(a.id)?.size || 0)
|
||||
@@ -368,6 +469,7 @@ export const adjustTablePositions = ({
|
||||
y: number,
|
||||
currentTableId: string
|
||||
): boolean => {
|
||||
// Check overlap with other tables
|
||||
for (const [tableId, pos] of tablePositions) {
|
||||
if (tableId === currentTableId) continue;
|
||||
|
||||
@@ -379,6 +481,26 @@ export const adjustTablePositions = ({
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check overlap with areas
|
||||
const { width: currentWidth, height: currentHeight } =
|
||||
getTableWidthAndHeight(currentTableId);
|
||||
const buffer = 50; // Add buffer around areas to keep tables away
|
||||
|
||||
for (const area of areas) {
|
||||
// Check if the table position would overlap with the area (with buffer)
|
||||
if (
|
||||
!(
|
||||
x + currentWidth < area.x - buffer ||
|
||||
x > area.x + area.width + buffer ||
|
||||
y + currentHeight < area.y - buffer ||
|
||||
y > area.y + area.height + buffer
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -462,19 +584,80 @@ export const adjustTablePositions = ({
|
||||
});
|
||||
};
|
||||
|
||||
// Position tables
|
||||
sortedTables.forEach((table, index) => {
|
||||
if (!positionedTables.has(table.id)) {
|
||||
const row = Math.floor(index / 6);
|
||||
const col = index % 6;
|
||||
const { width: tableWidth, height: tableHeight } =
|
||||
getTableWidthAndHeight(table.id);
|
||||
// Position connected tables first
|
||||
if (connectedTables.length < 100) {
|
||||
// Use relationship-based positioning for small sets of connected tables
|
||||
connectedTables.forEach((table, index) => {
|
||||
if (!positionedTables.has(table.id)) {
|
||||
const row = Math.floor(index / 6);
|
||||
const col = index % 6;
|
||||
const { width: tableWidth, height: tableHeight } =
|
||||
getTableWidthAndHeight(table.id);
|
||||
|
||||
const x = startX + col * (tableWidth + gapX * 2);
|
||||
const y = startY + row * (tableHeight + gapY * 2);
|
||||
positionTable(table, x, y);
|
||||
const x = startX + col * (tableWidth + gapX * 2);
|
||||
const y = startY + row * (tableHeight + gapY * 2);
|
||||
positionTable(table, x, y);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Use simple grid layout for large sets of connected tables
|
||||
connectedTables.forEach((table, index) => {
|
||||
if (!positionedTables.has(table.id)) {
|
||||
const row = Math.floor(index / 10); // More columns for large sets
|
||||
const col = index % 10;
|
||||
const { width: tableWidth, height: tableHeight } =
|
||||
getTableWidthAndHeight(table.id);
|
||||
|
||||
const x = startX + col * (tableWidth + gapX);
|
||||
const y = startY + row * (tableHeight + gapY);
|
||||
|
||||
// Direct positioning without relationship-based clustering
|
||||
const finalPos = findNonOverlappingPosition(x, y, table.id);
|
||||
table.x = finalPos.x;
|
||||
table.y = finalPos.y;
|
||||
tablePositions.set(table.id, { x: table.x, y: table.y });
|
||||
positionedTables.add(table.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Find the bottommost position of connected tables for isolated table placement
|
||||
let maxY = startY;
|
||||
for (const pos of tablePositions.values()) {
|
||||
const tableId = [...tablePositions.entries()].find(
|
||||
([, p]) => p === pos
|
||||
)?.[0];
|
||||
if (tableId) {
|
||||
const { height } = getTableWidthAndHeight(tableId);
|
||||
maxY = Math.max(maxY, pos.y + height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Position isolated tables after connected ones
|
||||
if (isolatedTables.length > 0) {
|
||||
const isolatedStartY = maxY + gapY * 2;
|
||||
const isolatedStartX = startX;
|
||||
|
||||
isolatedTables.forEach((table, index) => {
|
||||
if (!positionedTables.has(table.id)) {
|
||||
const row = Math.floor(index / 8); // More columns for isolated tables
|
||||
const col = index % 8;
|
||||
const { width: tableWidth, height: tableHeight } =
|
||||
getTableWidthAndHeight(table.id);
|
||||
|
||||
// Use a simple grid layout for isolated tables
|
||||
const x = isolatedStartX + col * (tableWidth + gapX);
|
||||
const y = isolatedStartY + row * (tableHeight + gapY);
|
||||
|
||||
// Find non-overlapping position
|
||||
const finalPos = findNonOverlappingPosition(x, y, table.id);
|
||||
table.x = finalPos.x;
|
||||
table.y = finalPos.y;
|
||||
tablePositions.set(table.id, { x: table.x, y: table.y });
|
||||
positionedTables.add(table.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply positions to tables
|
||||
tablesToAdjust.forEach((table) => {
|
||||
@@ -508,7 +691,7 @@ export const adjustTablePositions = ({
|
||||
}
|
||||
|
||||
return tables;
|
||||
};
|
||||
}
|
||||
|
||||
export const calcTableHeight = (table?: DBTable): number => {
|
||||
if (!table) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export const loadFromDatabaseMetadata = async ({
|
||||
databaseMetadata: DatabaseMetadata;
|
||||
diagramNumber?: number;
|
||||
databaseEdition?: DatabaseEdition;
|
||||
}): Promise<Diagram> => {
|
||||
}): Promise<{ diagram: Diagram; initialFilter?: { tableIds: string[] } }> => {
|
||||
const {
|
||||
fk_info: foreignKeys,
|
||||
views: views,
|
||||
@@ -93,11 +93,51 @@ export const loadFromDatabaseMetadata = async ({
|
||||
})
|
||||
: [];
|
||||
|
||||
const adjustedTables = adjustTablePositions({
|
||||
tables,
|
||||
relationships,
|
||||
mode: 'perSchema',
|
||||
});
|
||||
// For large diagrams, apply special handling
|
||||
const LARGE_DIAGRAM_THRESHOLD = 200;
|
||||
let adjustedTables = tables;
|
||||
let initialFilter: { tableIds: string[] } | undefined;
|
||||
|
||||
if (tables.length > LARGE_DIAGRAM_THRESHOLD) {
|
||||
// Create a set of table IDs that have relationships
|
||||
const tablesWithRelationships = new Set<string>();
|
||||
relationships.forEach((rel) => {
|
||||
tablesWithRelationships.add(rel.sourceTableId);
|
||||
tablesWithRelationships.add(rel.targetTableId);
|
||||
});
|
||||
|
||||
// Separate tables into connected and isolated
|
||||
const connectedTables = tables.filter((table) =>
|
||||
tablesWithRelationships.has(table.id)
|
||||
);
|
||||
const isolatedTables = tables.filter(
|
||||
(table) => !tablesWithRelationships.has(table.id)
|
||||
);
|
||||
|
||||
// Only reorder connected tables
|
||||
const reorderedConnectedTables = adjustTablePositions({
|
||||
tables: connectedTables,
|
||||
relationships,
|
||||
mode: 'perSchema',
|
||||
});
|
||||
|
||||
// Combine reordered connected tables with isolated tables
|
||||
adjustedTables = [...reorderedConnectedTables, ...isolatedTables];
|
||||
|
||||
// Set up filter to hide isolated tables if there are any
|
||||
if (isolatedTables.length > 0) {
|
||||
initialFilter = {
|
||||
tableIds: connectedTables.map((t) => t.id),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// For smaller diagrams, reorder all tables as before
|
||||
adjustedTables = adjustTablePositions({
|
||||
tables,
|
||||
relationships,
|
||||
mode: 'perSchema',
|
||||
});
|
||||
}
|
||||
|
||||
const sortedTables = adjustedTables.sort((a, b) => {
|
||||
if (a.isView === b.isView) {
|
||||
@@ -125,5 +165,5 @@ export const loadFromDatabaseMetadata = async ({
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
return diagram;
|
||||
return { diagram, initialFilter };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cloneDiagram } from './clone';
|
||||
import { diagramSchema, type Diagram } from './domain/diagram';
|
||||
import { generateDiagramId } from './utils';
|
||||
import { adjustTablePositions } from './domain/db-table';
|
||||
|
||||
export const runningIdGenerator = (): (() => string) => {
|
||||
let id = 0;
|
||||
@@ -36,5 +37,82 @@ export const diagramFromJSONInput = (json: string): Diagram => {
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
return cloneDiagramWithIds(diagram);
|
||||
const clonedDiagram = cloneDiagramWithIds(diagram);
|
||||
|
||||
// Apply reordering for large diagrams AFTER identifying which tables have relationships
|
||||
const LARGE_DIAGRAM_THRESHOLD = 200;
|
||||
|
||||
if (
|
||||
clonedDiagram.tables &&
|
||||
clonedDiagram.tables.length > LARGE_DIAGRAM_THRESHOLD &&
|
||||
clonedDiagram.relationships
|
||||
) {
|
||||
// Create a set of table IDs that have relationships
|
||||
const tablesWithRelationships = new Set<string>();
|
||||
clonedDiagram.relationships.forEach((rel) => {
|
||||
tablesWithRelationships.add(rel.sourceTableId);
|
||||
tablesWithRelationships.add(rel.targetTableId);
|
||||
});
|
||||
|
||||
// Filter tables to only those with relationships for reordering
|
||||
const tablesToReorder = clonedDiagram.tables.filter((table) =>
|
||||
tablesWithRelationships.has(table.id)
|
||||
);
|
||||
|
||||
// Apply reordering only to tables with relationships
|
||||
const reorderedTables = adjustTablePositions({
|
||||
tables: tablesToReorder,
|
||||
relationships: clonedDiagram.relationships || [],
|
||||
areas: clonedDiagram.areas || [],
|
||||
mode: 'all',
|
||||
});
|
||||
|
||||
// Update positions for reordered tables
|
||||
clonedDiagram.tables = clonedDiagram.tables.map((table) => {
|
||||
const reorderedTable = reorderedTables.find(
|
||||
(t) => t.id === table.id
|
||||
);
|
||||
if (reorderedTable) {
|
||||
return {
|
||||
...table,
|
||||
x: reorderedTable.x,
|
||||
y: reorderedTable.y,
|
||||
};
|
||||
}
|
||||
return table;
|
||||
});
|
||||
}
|
||||
|
||||
return clonedDiagram;
|
||||
};
|
||||
|
||||
export const getInitialFilterForLargeDiagram = (
|
||||
diagram: Diagram
|
||||
): { tableIds?: string[] } | null => {
|
||||
const LARGE_DIAGRAM_THRESHOLD = 200;
|
||||
|
||||
if (
|
||||
diagram.tables &&
|
||||
diagram.tables.length > LARGE_DIAGRAM_THRESHOLD &&
|
||||
diagram.relationships
|
||||
) {
|
||||
// Create a set of table IDs that have relationships
|
||||
const tablesWithRelationships = new Set<string>();
|
||||
diagram.relationships.forEach((rel) => {
|
||||
tablesWithRelationships.add(rel.sourceTableId);
|
||||
tablesWithRelationships.add(rel.targetTableId);
|
||||
});
|
||||
|
||||
// Return only tables with relationships to be shown (filter will hide the rest)
|
||||
const tablesToShow = diagram.tables
|
||||
.filter((table) => tablesWithRelationships.has(table.id))
|
||||
.map((table) => table.id);
|
||||
|
||||
// If there are tables to filter out, return the filter
|
||||
if (tablesToShow.length < diagram.tables.length) {
|
||||
return { tableIds: tablesToShow };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { TreeNode } from '@/components/tree-view/tree';
|
||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
|
||||
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
import type {
|
||||
GroupingMode,
|
||||
NodeContext,
|
||||
@@ -26,6 +25,7 @@ import type {
|
||||
} from './types';
|
||||
import { generateTreeDataByAreas, generateTreeDataBySchemas } from './utils';
|
||||
import { FilterItemActions } from './filter-item-actions';
|
||||
import { databasesWithSchemas } from '@/lib/domain';
|
||||
|
||||
export interface CanvasFilterProps {
|
||||
onClose: () => void;
|
||||
@@ -63,7 +63,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
|
||||
);
|
||||
|
||||
const databaseWithSchemas = useMemo(
|
||||
() => !!defaultSchemas[databaseType],
|
||||
() => databasesWithSchemas.includes(databaseType),
|
||||
[databaseType]
|
||||
);
|
||||
|
||||
|
||||
@@ -119,8 +119,15 @@ const initialEdges: EdgeType[] = [];
|
||||
|
||||
const tableToTableNode = (
|
||||
table: DBTable,
|
||||
filter: DiagramFilter | undefined,
|
||||
databaseType: DatabaseType
|
||||
{
|
||||
filter,
|
||||
databaseType,
|
||||
filterLoading,
|
||||
}: {
|
||||
filter?: DiagramFilter;
|
||||
databaseType: DatabaseType;
|
||||
filterLoading: boolean;
|
||||
}
|
||||
): TableNodeType => {
|
||||
// Always use absolute position for now
|
||||
const position = { x: table.x, y: table.y };
|
||||
@@ -134,19 +141,28 @@ const tableToTableNode = (
|
||||
isOverlapping: false,
|
||||
},
|
||||
width: table.width ?? MIN_TABLE_SIZE,
|
||||
hidden: !filterTable({
|
||||
table: { id: table.id, schema: table.schema },
|
||||
filter,
|
||||
options: { defaultSchema: defaultSchemas[databaseType] },
|
||||
}),
|
||||
hidden:
|
||||
!filterTable({
|
||||
table: { id: table.id, schema: table.schema },
|
||||
filter,
|
||||
options: { defaultSchema: defaultSchemas[databaseType] },
|
||||
}) || filterLoading,
|
||||
};
|
||||
};
|
||||
|
||||
const areaToAreaNode = (
|
||||
area: Area,
|
||||
tables: DBTable[],
|
||||
filter?: DiagramFilter,
|
||||
databaseType?: DatabaseType
|
||||
{
|
||||
tables,
|
||||
filter,
|
||||
databaseType,
|
||||
filterLoading,
|
||||
}: {
|
||||
tables: DBTable[];
|
||||
filter?: DiagramFilter;
|
||||
databaseType: DatabaseType;
|
||||
filterLoading: boolean;
|
||||
}
|
||||
): AreaNodeType => {
|
||||
// Get all tables in this area
|
||||
const tablesInArea = tables.filter((t) => t.parentAreaId === area.id);
|
||||
@@ -159,8 +175,7 @@ const areaToAreaNode = (
|
||||
table: { id: table.id, schema: table.schema },
|
||||
filter,
|
||||
options: {
|
||||
defaultSchema:
|
||||
defaultSchemas[databaseType || DatabaseType.GENERIC],
|
||||
defaultSchema: defaultSchemas[databaseType],
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -173,7 +188,7 @@ const areaToAreaNode = (
|
||||
width: area.width,
|
||||
height: area.height,
|
||||
zIndex: -10,
|
||||
hidden: !hasVisibleTable,
|
||||
hidden: !hasVisibleTable || filterLoading,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -225,13 +240,13 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
showFilter,
|
||||
setShowFilter,
|
||||
} = useCanvas();
|
||||
const { filter } = useDiagramFilter();
|
||||
const { filter, loading: filterLoading } = useDiagramFilter();
|
||||
|
||||
const [isInitialLoadingNodes, setIsInitialLoadingNodes] = useState(true);
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<NodeType>(
|
||||
initialTables.map((table) =>
|
||||
tableToTableNode(table, filter, databaseType)
|
||||
tableToTableNode(table, { filter, databaseType, filterLoading })
|
||||
)
|
||||
);
|
||||
const [edges, setEdges, onEdgesChange] =
|
||||
@@ -245,12 +260,12 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
|
||||
useEffect(() => {
|
||||
const initialNodes = initialTables.map((table) =>
|
||||
tableToTableNode(table, filter, databaseType)
|
||||
tableToTableNode(table, { filter, databaseType, filterLoading })
|
||||
);
|
||||
if (equal(initialNodes, nodes)) {
|
||||
setIsInitialLoadingNodes(false);
|
||||
}
|
||||
}, [initialTables, nodes, filter, databaseType]);
|
||||
}, [initialTables, nodes, filter, databaseType, filterLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitialLoadingNodes) {
|
||||
@@ -413,7 +428,11 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
...tables.map((table) => {
|
||||
const isOverlapping =
|
||||
(overlapGraph.graph.get(table.id) ?? []).length > 0;
|
||||
const node = tableToTableNode(table, filter, databaseType);
|
||||
const node = tableToTableNode(table, {
|
||||
filter,
|
||||
databaseType,
|
||||
filterLoading,
|
||||
});
|
||||
|
||||
// Check if table uses the highlighted custom type
|
||||
let hasHighlightedCustomType = false;
|
||||
@@ -435,7 +454,12 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
};
|
||||
}),
|
||||
...areas.map((area) =>
|
||||
areaToAreaNode(area, tables, filter, databaseType)
|
||||
areaToAreaNode(area, {
|
||||
tables,
|
||||
filter,
|
||||
databaseType,
|
||||
filterLoading,
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
@@ -456,6 +480,7 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
overlapGraph.graph,
|
||||
highlightOverlappingTables,
|
||||
highlightedCustomType,
|
||||
filterLoading,
|
||||
]);
|
||||
|
||||
const prevFilter = useRef<DiagramFilter | undefined>(undefined);
|
||||
@@ -487,22 +512,26 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
}
|
||||
}, [filter, fitView, tables, setOverlapGraph, databaseType]);
|
||||
|
||||
// Handle parent area updates when tables move
|
||||
const tablePositions = useMemo(
|
||||
() => tables.map((t) => ({ id: t.id, x: t.x, y: t.y })),
|
||||
[tables]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const checkParentAreas = debounce(() => {
|
||||
const updatedTables = updateTablesParentAreas(tables, areas);
|
||||
const visibleTables = nodes
|
||||
.filter((node) => node.type === 'table' && !node.hidden)
|
||||
.map((node) => (node as TableNodeType).data.table);
|
||||
const visibleAreas = nodes
|
||||
.filter((node) => node.type === 'area' && !node.hidden)
|
||||
.map((node) => (node as AreaNodeType).data.area);
|
||||
|
||||
const updatedTables = updateTablesParentAreas(
|
||||
visibleTables,
|
||||
visibleAreas
|
||||
);
|
||||
const needsUpdate: Array<{
|
||||
id: string;
|
||||
parentAreaId: string | null;
|
||||
}> = [];
|
||||
|
||||
updatedTables.forEach((newTable, index) => {
|
||||
const oldTable = tables[index];
|
||||
const oldTable = visibleTables[index];
|
||||
if (
|
||||
oldTable &&
|
||||
(!!newTable.parentAreaId || !!oldTable.parentAreaId) &&
|
||||
@@ -536,7 +565,7 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
|
||||
}, 300);
|
||||
|
||||
checkParentAreas();
|
||||
}, [tablePositions, areas, updateTablesState, tables]);
|
||||
}, [nodes, updateTablesState]);
|
||||
|
||||
const onConnectHandler = useCallback(
|
||||
async (params: AddEdgeParams) => {
|
||||
|
||||
@@ -10,18 +10,11 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '@/components/sidebar/sidebar';
|
||||
import {
|
||||
Twitter,
|
||||
BookOpen,
|
||||
Group,
|
||||
FileType,
|
||||
Plus,
|
||||
FolderOpen,
|
||||
} from 'lucide-react';
|
||||
import { BookOpen, Group, FileType, Plus, FolderOpen } from 'lucide-react';
|
||||
import { SquareStack, Table, Workflow } from 'lucide-react';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DiscordLogoIcon } from '@radix-ui/react-icons';
|
||||
import { DiscordLogoIcon, TwitterLogoIcon } from '@radix-ui/react-icons';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import ChartDBLogo from '@/assets/logo-light.png';
|
||||
import ChartDBDarkLogo from '@/assets/logo-dark.png';
|
||||
@@ -53,7 +46,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
const diagramItems: SidebarItem[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('menu.file.new'),
|
||||
title: t('editor_sidebar.new_diagram'),
|
||||
icon: Plus,
|
||||
onClick: () => {
|
||||
openCreateDiagramDialog();
|
||||
@@ -61,7 +54,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
title: t('menu.file.open'),
|
||||
title: t('editor_sidebar.browse'),
|
||||
icon: FolderOpen,
|
||||
onClick: () => {
|
||||
openOpenDiagramDialog();
|
||||
@@ -75,7 +68,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
const baseItems: SidebarItem[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('side_panel.tables_section.tables'),
|
||||
title: t('editor_sidebar.tables'),
|
||||
icon: Table,
|
||||
onClick: () => {
|
||||
showSidePanel();
|
||||
@@ -84,7 +77,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
active: selectedSidebarSection === 'tables',
|
||||
},
|
||||
{
|
||||
title: t('side_panel.relationships_section.relationships'),
|
||||
title: t('editor_sidebar.relationships'),
|
||||
icon: Workflow,
|
||||
onClick: () => {
|
||||
showSidePanel();
|
||||
@@ -93,7 +86,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
active: selectedSidebarSection === 'relationships',
|
||||
},
|
||||
{
|
||||
title: t('side_panel.areas_section.areas'),
|
||||
title: t('editor_sidebar.areas'),
|
||||
icon: Group,
|
||||
onClick: () => {
|
||||
showSidePanel();
|
||||
@@ -104,9 +97,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
...(dependencies && dependencies.length > 0
|
||||
? [
|
||||
{
|
||||
title: t(
|
||||
'side_panel.dependencies_section.dependencies'
|
||||
),
|
||||
title: t('editor_sidebar.dependencies'),
|
||||
icon: SquareStack,
|
||||
onClick: () => {
|
||||
showSidePanel();
|
||||
@@ -119,9 +110,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
...(databaseType === DatabaseType.POSTGRESQL
|
||||
? [
|
||||
{
|
||||
title: t(
|
||||
'side_panel.custom_types_section.custom_types'
|
||||
),
|
||||
title: t('editor_sidebar.custom_types'),
|
||||
icon: FileType,
|
||||
onClick: () => {
|
||||
showSidePanel();
|
||||
@@ -153,7 +142,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
icon: Twitter,
|
||||
icon: TwitterLogoIcon,
|
||||
onClick: () =>
|
||||
window.open(
|
||||
'https://x.com/intent/follow?screen_name=jonathanfishner',
|
||||
@@ -162,7 +151,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
title: 'Docs',
|
||||
icon: BookOpen,
|
||||
onClick: () => window.open('https://docs.chartdb.io', '_blank'),
|
||||
active: false,
|
||||
@@ -174,7 +163,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
return (
|
||||
<Sidebar
|
||||
side="left"
|
||||
collapsible="icon"
|
||||
collapsible="icon-extended"
|
||||
variant="sidebar"
|
||||
className="relative h-full"
|
||||
>
|
||||
@@ -205,14 +194,21 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
{diagramItems.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton
|
||||
className="hover:bg-gray-200 data-[active=true]:bg-gray-100 data-[active=true]:text-pink-600 data-[active=true]:hover:bg-pink-100 dark:hover:bg-gray-800 dark:data-[active=true]:bg-gray-900 dark:data-[active=true]:text-pink-400 dark:data-[active=true]:hover:bg-pink-950"
|
||||
className="justify-center space-y-0.5 !px-0 hover:bg-gray-200 data-[active=true]:bg-gray-100 data-[active=true]:text-pink-600 data-[active=true]:hover:bg-pink-100 dark:hover:bg-gray-800 dark:data-[active=true]:bg-gray-900 dark:data-[active=true]:text-pink-400 dark:data-[active=true]:hover:bg-pink-950"
|
||||
isActive={item.active}
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
>
|
||||
<button onClick={item.onClick}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
<span>
|
||||
{item.title
|
||||
.split(' ')
|
||||
.map((word, index) => (
|
||||
<div key={index}>
|
||||
{word}
|
||||
</div>
|
||||
))}
|
||||
</span>
|
||||
</button>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
@@ -223,14 +219,21 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
{baseItems.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton
|
||||
className="hover:bg-gray-200 data-[active=true]:bg-gray-100 data-[active=true]:text-pink-600 data-[active=true]:hover:bg-pink-100 dark:hover:bg-gray-800 dark:data-[active=true]:bg-gray-900 dark:data-[active=true]:text-pink-400 dark:data-[active=true]:hover:bg-pink-950"
|
||||
className="justify-center space-y-0.5 !px-0 hover:bg-gray-200 data-[active=true]:bg-gray-100 data-[active=true]:text-pink-600 data-[active=true]:hover:bg-pink-100 dark:hover:bg-gray-800 dark:data-[active=true]:bg-gray-900 dark:data-[active=true]:text-pink-400 dark:data-[active=true]:hover:bg-pink-950"
|
||||
isActive={item.active}
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
>
|
||||
<button onClick={item.onClick}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
<span>
|
||||
{item.title
|
||||
.split(' ')
|
||||
.map((word, index) => (
|
||||
<div key={index}>
|
||||
{word}
|
||||
</div>
|
||||
))}
|
||||
</span>
|
||||
</button>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
@@ -250,10 +253,9 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = () => {
|
||||
</span>
|
||||
)}
|
||||
<SidebarMenuButton
|
||||
className="hover:bg-gray-200 data-[active=true]:bg-gray-100 data-[active=true]:text-pink-600 data-[active=true]:hover:bg-pink-100 dark:hover:bg-gray-800 dark:data-[active=true]:bg-gray-900 dark:data-[active=true]:text-pink-400 dark:data-[active=true]:hover:bg-pink-950"
|
||||
className="justify-center space-y-0.5 !px-0 hover:bg-gray-200 data-[active=true]:bg-gray-100 data-[active=true]:text-pink-600 data-[active=true]:hover:bg-pink-100 dark:hover:bg-gray-800 dark:data-[active=true]:bg-gray-900 dark:data-[active=true]:text-pink-400 dark:data-[active=true]:hover:bg-pink-950"
|
||||
isActive={item.active}
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
>
|
||||
<button onClick={item.onClick}>
|
||||
<item.icon />
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Ellipsis, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import type { DBIndex } from '@/lib/domain/db-index';
|
||||
import {
|
||||
databaseIndexTypes,
|
||||
type DBIndex,
|
||||
type IndexType,
|
||||
} from '@/lib/domain/db-index';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import {
|
||||
Popover,
|
||||
@@ -20,6 +24,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
|
||||
export interface TableIndexProps {
|
||||
index: DBIndex;
|
||||
@@ -28,6 +33,11 @@ export interface TableIndexProps {
|
||||
fields: DBField[];
|
||||
}
|
||||
|
||||
const allIndexTypeOptions: { label: string; value: IndexType }[] = [
|
||||
{ label: 'B-tree (default)', value: 'btree' },
|
||||
{ label: 'Hash', value: 'hash' },
|
||||
];
|
||||
|
||||
export const TableIndex: React.FC<TableIndexProps> = ({
|
||||
fields,
|
||||
index,
|
||||
@@ -35,14 +45,51 @@ export const TableIndex: React.FC<TableIndexProps> = ({
|
||||
removeIndex,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { databaseType } = useChartDB();
|
||||
const fieldOptions = fields.map((field) => ({
|
||||
label: field.name,
|
||||
value: field.id,
|
||||
}));
|
||||
const updateIndexFields = (fieldIds: string | string[]) => {
|
||||
const ids = Array.isArray(fieldIds) ? fieldIds : [fieldIds];
|
||||
updateIndex({ fieldIds: ids });
|
||||
};
|
||||
const updateIndexFields = useCallback(
|
||||
(fieldIds: string | string[]) => {
|
||||
const ids = Array.isArray(fieldIds) ? fieldIds : [fieldIds];
|
||||
|
||||
// For hash indexes, only keep the last selected field
|
||||
if (index.type === 'hash' && ids.length > 0) {
|
||||
updateIndex({ fieldIds: [ids[ids.length - 1]] });
|
||||
} else {
|
||||
updateIndex({ fieldIds: ids });
|
||||
}
|
||||
},
|
||||
[index.type, updateIndex]
|
||||
);
|
||||
|
||||
const indexTypeOptions = useMemo(
|
||||
() =>
|
||||
allIndexTypeOptions.filter((option) =>
|
||||
databaseIndexTypes[databaseType]?.includes(option.value)
|
||||
),
|
||||
[databaseType]
|
||||
);
|
||||
|
||||
const updateIndexType = useCallback(
|
||||
(value: string | string[]) => {
|
||||
{
|
||||
const newType = value as IndexType;
|
||||
// If switching to hash and multiple fields are selected, keep only the first
|
||||
if (newType === 'hash' && index.fieldIds.length > 1) {
|
||||
updateIndex({
|
||||
type: newType,
|
||||
fieldIds: [index.fieldIds[0]],
|
||||
});
|
||||
} else {
|
||||
updateIndex({ type: newType });
|
||||
}
|
||||
}
|
||||
},
|
||||
[updateIndex, index.fieldIds]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-row justify-between gap-2 p-1">
|
||||
<SelectBox
|
||||
@@ -135,6 +182,23 @@ export const TableIndex: React.FC<TableIndexProps> = ({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{indexTypeOptions.length > 0 ? (
|
||||
<div className="mt-2 flex flex-col gap-2">
|
||||
<Label
|
||||
htmlFor="indexType"
|
||||
className="text-subtitle"
|
||||
>
|
||||
{t(
|
||||
'side_panel.tables_section.table.index_actions.index_type'
|
||||
)}
|
||||
</Label>
|
||||
<SelectBox
|
||||
options={indexTypeOptions}
|
||||
value={index.type || 'btree'}
|
||||
onChange={updateIndexType}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Separator orientation="horizontal" />
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -156,13 +156,13 @@ export const Menu: React.FC<MenuProps> = () => {
|
||||
return (
|
||||
<Menubar className="h-8 border-none py-2 shadow-none md:h-10 md:py-0">
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>{t('menu.file.file')}</MenubarTrigger>
|
||||
<MenubarTrigger>{t('menu.databases.databases')}</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem onClick={createNewDiagram}>
|
||||
{t('menu.file.new')}
|
||||
{t('menu.databases.new')}
|
||||
</MenubarItem>
|
||||
<MenubarItem onClick={openDiagram}>
|
||||
{t('menu.file.open')}
|
||||
{t('menu.databases.browse')}
|
||||
<MenubarShortcut>
|
||||
{
|
||||
keyboardShortcutsForOS[
|
||||
@@ -172,7 +172,7 @@ export const Menu: React.FC<MenuProps> = () => {
|
||||
</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem onClick={updateDiagramUpdatedAt}>
|
||||
{t('menu.file.save')}
|
||||
{t('menu.databases.save')}
|
||||
<MenubarShortcut>
|
||||
{
|
||||
keyboardShortcutsForOS[
|
||||
@@ -184,7 +184,7 @@ export const Menu: React.FC<MenuProps> = () => {
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
{t('menu.file.import')}
|
||||
{t('menu.databases.import')}
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem onClick={openImportDiagramDialog}>
|
||||
@@ -253,7 +253,7 @@ export const Menu: React.FC<MenuProps> = () => {
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
{t('menu.file.export_sql')}
|
||||
{t('menu.databases.export_sql')}
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
{databaseType === DatabaseType.GENERIC ? (
|
||||
@@ -336,7 +336,7 @@ export const Menu: React.FC<MenuProps> = () => {
|
||||
</MenubarSub>
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
{t('menu.file.export_as')}
|
||||
{t('menu.databases.export_as')}
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem onClick={exportPNG}>PNG</MenubarItem>
|
||||
@@ -362,10 +362,8 @@ export const Menu: React.FC<MenuProps> = () => {
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('menu.file.delete_diagram')}
|
||||
{t('menu.databases.delete_diagram')}
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>{t('menu.file.exit')}</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
|
||||
Reference in New Issue
Block a user